gitwebhooks 2.4.0__py3-none-any.whl → 2.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. gitwebhooks/__init__.py +1 -1
  2. gitwebhooks/auth/custom.py +6 -0
  3. gitwebhooks/auth/factory.py +0 -25
  4. gitwebhooks/auth/gitee.py +6 -0
  5. gitwebhooks/auth/gitlab.py +6 -0
  6. gitwebhooks/cli/__init__.py +31 -7
  7. gitwebhooks/cli/help.py +27 -96
  8. gitwebhooks/cli/init_wizard.py +13 -7
  9. gitwebhooks/cli/prompts.py +9 -205
  10. gitwebhooks/cli/serve.py +4 -39
  11. gitwebhooks/cli/service.py +13 -17
  12. gitwebhooks/config/loader.py +28 -10
  13. gitwebhooks/config/models.py +0 -1
  14. gitwebhooks/config/registry.py +0 -38
  15. gitwebhooks/config/server.py +1 -1
  16. gitwebhooks/handlers/base.py +0 -10
  17. gitwebhooks/handlers/custom.py +4 -7
  18. gitwebhooks/handlers/factory.py +0 -37
  19. gitwebhooks/handlers/gitee.py +4 -8
  20. gitwebhooks/handlers/github.py +2 -6
  21. gitwebhooks/handlers/gitlab.py +2 -6
  22. gitwebhooks/handlers/request.py +1 -1
  23. gitwebhooks/main/__init__.py +30 -17
  24. gitwebhooks/main/version.py +0 -2
  25. gitwebhooks/models/request.py +0 -30
  26. gitwebhooks/server.py +0 -2
  27. gitwebhooks/utils/executor.py +80 -5
  28. gitwebhooks/utils/systemd.py +2 -46
  29. {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/METADATA +37 -27
  30. gitwebhooks-2.4.2.dist-info/RECORD +51 -0
  31. {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/WHEEL +1 -1
  32. gitwebhooks-2.4.0.dist-info/RECORD +0 -51
  33. {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/entry_points.txt +0 -0
  34. {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/licenses/LICENSE +0 -0
  35. {gitwebhooks-2.4.0.dist-info → gitwebhooks-2.4.2.dist-info}/top_level.txt +0 -0
gitwebhooks/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  Automated deployment server supporting GitHub, Gitee, GitLab, and custom webhooks.
4
4
  """
5
5
 
6
- __version__ = '2.4.0'
6
+ __version__ = '2.4.2'
7
7
 
8
8
  # Export main classes for convenient import paths
9
9
  from .server import WebhookServer
@@ -34,6 +34,12 @@ class CustomTokenVerifier(SignatureVerifier):
34
34
  if not self.verify_enabled:
35
35
  return SignatureVerificationResult.success()
36
36
 
37
+ # An empty secret means verification is not actually configured.
38
+ # Without this guard an empty token would match and let an
39
+ # unauthenticated request through (which would run the deploy command).
40
+ if not secret:
41
+ return SignatureVerificationResult.failure('Secret not configured')
42
+
37
43
  if signature == secret:
38
44
  return SignatureVerificationResult.success()
39
45
  else:
@@ -4,12 +4,10 @@ Creates corresponding verifier instances based on provider type.
4
4
  """
5
5
 
6
6
  from gitwebhooks.models.provider import Provider
7
- from gitwebhooks.auth.verifier import SignatureVerifier
8
7
  from gitwebhooks.auth.github import GithubSignatureVerifier
9
8
  from gitwebhooks.auth.gitee import GiteeSignatureVerifier
10
9
  from gitwebhooks.auth.gitlab import GitlabTokenVerifier
11
10
  from gitwebhooks.auth.custom import CustomTokenVerifier
12
- from gitwebhooks.utils.exceptions import UnsupportedProviderError
13
11
 
14
12
 
15
13
  class VerifierFactory:
@@ -25,29 +23,6 @@ class VerifierFactory:
25
23
  Provider.CUSTOM: None, # Custom verifier created dynamically
26
24
  }
27
25
 
28
- @classmethod
29
- def get_verifier(cls, provider: Provider,
30
- verify_enabled: bool = True) -> SignatureVerifier:
31
- """Get verifier for the specified provider
32
-
33
- Args:
34
- provider: Provider type
35
- verify_enabled: Whether verification is enabled (CUSTOM only)
36
-
37
- Returns:
38
- SignatureVerifier instance
39
-
40
- Raises:
41
- UnsupportedProviderError: Unsupported provider
42
- """
43
- if provider == Provider.CUSTOM:
44
- return CustomTokenVerifier(verify_enabled=verify_enabled)
45
-
46
- verifier = cls._verifiers.get(provider)
47
- if verifier is None:
48
- raise UnsupportedProviderError(f'No verifier for provider: {provider}')
49
- return verifier
50
-
51
26
  @classmethod
52
27
  def create_github_verifier(cls) -> GithubSignatureVerifier:
53
28
  """Create GitHub verifier (convenience method)"""
gitwebhooks/auth/gitee.py CHANGED
@@ -51,6 +51,12 @@ class GiteeSignatureVerifier(SignatureVerifier):
51
51
  if signature is None:
52
52
  return SignatureVerificationResult.failure('Missing signature or password')
53
53
 
54
+ # An empty secret means verification is not actually configured.
55
+ # Without this guard an empty token would match and let an
56
+ # unauthenticated request through (which would run the deploy command).
57
+ if not secret:
58
+ return SignatureVerificationResult.failure('Secret not configured')
59
+
54
60
  # Step 1: Try password validation first (direct comparison)
55
61
  if hmac.compare_digest(signature, secret):
56
62
  return SignatureVerificationResult.success()
@@ -23,6 +23,12 @@ class GitlabTokenVerifier(SignatureVerifier):
23
23
  Returns:
24
24
  SignatureVerificationResult instance
25
25
  """
26
+ # An empty secret means verification is not actually configured.
27
+ # Without this guard an empty token would match and let an
28
+ # unauthenticated request through (which would run the deploy command).
29
+ if not secret:
30
+ return SignatureVerificationResult.failure('Secret not configured')
31
+
26
32
  if signature == secret:
27
33
  return SignatureVerificationResult.success()
28
34
  else:
@@ -7,6 +7,22 @@ from gitwebhooks.cli.config import cmd_init, cmd_view
7
7
  from gitwebhooks.cli.help import cmd_help
8
8
  from gitwebhooks.cli.serve import cmd_serve
9
9
 
10
+ # Top-level subparsers keyed by name, so the `help` subcommand can render
11
+ # exactly the same text argparse prints for `<subcommand> --help`.
12
+ _SUBPARSERS = {}
13
+
14
+
15
+ def get_subparser(topic):
16
+ """Get a registered top-level subparser by name.
17
+
18
+ Args:
19
+ topic: Subcommand name ('serve' or 'config')
20
+
21
+ Returns:
22
+ The ArgumentParser for that subcommand, or None if unknown
23
+ """
24
+ return _SUBPARSERS.get(topic)
25
+
10
26
 
11
27
  def register_subparsers(subparsers):
12
28
  """Register all CLI subparsers
@@ -38,9 +54,9 @@ def register_serve_subparser(subparsers):
38
54
  """
39
55
  serve_parser = subparsers.add_parser(
40
56
  'serve',
41
- add_help=False,
42
57
  help='Start webhook server or manage systemd service'
43
58
  )
59
+ _SUBPARSERS['serve'] = serve_parser
44
60
  serve_parser.add_argument(
45
61
  '-c', '--config',
46
62
  dest='config',
@@ -49,7 +65,10 @@ def register_serve_subparser(subparsers):
49
65
  serve_subparsers = serve_parser.add_subparsers(dest='serve_action')
50
66
 
51
67
  # install action
52
- install_parser = serve_subparsers.add_parser('install', add_help=False)
68
+ install_parser = serve_subparsers.add_parser(
69
+ 'install',
70
+ help='Install systemd service'
71
+ )
53
72
  install_parser.add_argument(
54
73
  '--force',
55
74
  action='store_true',
@@ -74,7 +93,10 @@ def register_serve_subparser(subparsers):
74
93
  install_parser.set_defaults(func=cmd_serve)
75
94
 
76
95
  # uninstall action
77
- uninstall_parser = serve_subparsers.add_parser('uninstall', add_help=False)
96
+ uninstall_parser = serve_subparsers.add_parser(
97
+ 'uninstall',
98
+ help='Uninstall systemd service'
99
+ )
78
100
  uninstall_parser.add_argument(
79
101
  '--purge',
80
102
  action='store_true',
@@ -97,7 +119,7 @@ def register_help_subparser(subparsers):
97
119
  """
98
120
  help_parser = subparsers.add_parser(
99
121
  'help',
100
- add_help=False
122
+ help='Show help information'
101
123
  )
102
124
  help_parser.add_argument(
103
125
  'topic',
@@ -118,15 +140,14 @@ def register_config_subparser(subparsers):
118
140
  """
119
141
  config_parser = subparsers.add_parser(
120
142
  'config',
121
- add_help=False,
122
143
  help='Manage gitwebhooks configuration'
123
144
  )
145
+ _SUBPARSERS['config'] = config_parser
124
146
  config_subparsers = config_parser.add_subparsers(dest='config_action')
125
147
 
126
148
  # init action
127
149
  init_parser = config_subparsers.add_parser(
128
150
  'init',
129
- add_help=False,
130
151
  help='Initialize configuration file using interactive wizard'
131
152
  )
132
153
  init_parser.add_argument(
@@ -138,7 +159,10 @@ def register_config_subparser(subparsers):
138
159
  init_parser.set_defaults(func=cmd_init)
139
160
 
140
161
  # view action
141
- view_parser = config_subparsers.add_parser('view', add_help=False)
162
+ view_parser = config_subparsers.add_parser(
163
+ 'view',
164
+ help='View configuration file content'
165
+ )
142
166
  view_parser.add_argument(
143
167
  '-c',
144
168
  '--config',
gitwebhooks/cli/help.py CHANGED
@@ -1,6 +1,10 @@
1
1
  """Help command module
2
2
 
3
3
  Provides help information display functionality for gitwebhooks-cli.
4
+
5
+ All help output is generated by argparse from the parser definitions, so
6
+ ``<command> -h``/``--help`` and ``gitwebhooks-cli help <command>`` always
7
+ show exactly the same text.
4
8
  """
5
9
 
6
10
  import argparse
@@ -17,7 +21,7 @@ def cmd_help(args: argparse.Namespace) -> int:
17
21
  - topic: Optional subcommand name to show help for
18
22
 
19
23
  Returns:
20
- Exit code (0 = success)
24
+ Exit code (0 = success, 1 = unknown topic)
21
25
 
22
26
  Examples:
23
27
  >>> args = argparse.Namespace(topic=None)
@@ -29,16 +33,23 @@ def cmd_help(args: argparse.Namespace) -> int:
29
33
  """
30
34
  # Import here to avoid circular dependency
31
35
  from gitwebhooks.main import get_parser
36
+ from gitwebhooks.cli import get_subparser
32
37
 
33
38
  parser = get_parser()
39
+ topic = getattr(args, 'topic', None)
34
40
 
35
- if hasattr(args, 'topic') and args.topic:
36
- # Show subcommand help
37
- show_subcommand_help(parser, args.topic)
38
- else:
39
- # Show main help
41
+ if not topic:
40
42
  show_main_help(parser)
43
+ return 0
44
+
45
+ if get_subparser(topic) is None:
46
+ print(f"Error: Unknown command '{topic}'", file=sys.stderr)
47
+ print("Available commands: serve, config", file=sys.stderr)
48
+ print()
49
+ print("Use 'gitwebhooks-cli help' to see main help", file=sys.stderr)
50
+ return 1
41
51
 
52
+ show_subcommand_help(parser, topic)
42
53
  return 0
43
54
 
44
55
 
@@ -51,105 +62,25 @@ def show_main_help(parser: argparse.ArgumentParser) -> None:
51
62
  Returns:
52
63
  None
53
64
  """
54
- # Custom help output
55
- help_text = """Git Webhooks Server - Automated deployment webhook handler
56
-
57
- Usage:
58
- gitwebhooks-cli [-v] [-c CONFIG] <command> [<args>]
59
-
60
- Commands:
61
- serve Start webhook server or manage systemd service
62
- config Manage configuration file
63
- help Show help information
64
-
65
- Options:
66
- -v, --version Show version information
67
- -c, --config Path to configuration file (default: auto-discover)
68
-
69
- Examples:
70
- gitwebhooks-cli serve # Start server
71
- gitwebhooks-cli serve install # Install systemd service
72
- gitwebhooks-cli config init # Initialize configuration
73
- gitwebhooks-cli help serve # Show serve command help
74
-
75
- For more information on a specific command:
76
- gitwebhooks-cli help <command>
77
- """
78
- print(help_text)
65
+ print(parser.format_help(), end='')
79
66
 
80
67
 
81
68
  def show_subcommand_help(parser: argparse.ArgumentParser, topic: str) -> None:
82
69
  """Display help for a specific subcommand.
83
70
 
84
71
  Args:
85
- parser: The main argument parser
72
+ parser: The main argument parser (unused, kept for call compatibility)
86
73
  topic: Subcommand name
87
74
 
88
75
  Returns:
89
76
  None
90
77
  """
91
- # Map topic to help text
92
- help_texts = {
93
- 'serve': """Command: serve
94
- Start webhook server or manage systemd service
95
-
96
- Usage:
97
- gitwebhooks-cli serve [-c CONFIG]
98
- gitwebhooks-cli serve install [--force] [--dry-run] [--verbose]
99
- gitwebhooks-cli serve uninstall [--purge]
100
-
101
- Subcommands:
102
- (none) Start the webhook server
103
- install Install systemd service
104
- uninstall Uninstall systemd service
105
-
106
- Options:
107
- -c, --config Path to configuration file
108
-
109
- Install options:
110
- --force Force overwrite existing service
111
- --dry-run Preview mode, don't make changes
112
- --verbose Increase verbosity
113
-
114
- Uninstall options:
115
- --purge Also remove configuration file
116
-
117
- Examples:
118
- gitwebhooks-cli serve # Start server
119
- gitwebhooks-cli serve -c /etc/webhooks.ini # Start with custom config
120
- gitwebhooks-cli serve install # Install service
121
- gitwebhooks-cli serve install --force # Force overwrite
122
- gitwebhooks-cli serve uninstall # Uninstall service
123
- """,
124
- 'config': """Command: config
125
- Manage configuration file
126
-
127
- Usage:
128
- gitwebhooks-cli config init [level]
129
- gitwebhooks-cli config view [-c CONFIG]
130
-
131
- Subcommands:
132
- init Initialize configuration file
133
- view View configuration file
134
-
135
- Init arguments:
136
- level Configuration level (system, local, user)
137
-
138
- View options:
139
- -c, --config Path to configuration file
140
-
141
- Examples:
142
- gitwebhooks-cli config init # Interactive init
143
- gitwebhooks-cli config init user # User-level config
144
- gitwebhooks-cli config init system # System-level config (requires sudo)
145
- gitwebhooks-cli config view # View current config
146
- """,
147
- }
148
-
149
- if topic in help_texts:
150
- print(help_texts[topic])
151
- else:
78
+ # Import here to avoid circular dependency
79
+ from gitwebhooks.cli import get_subparser
80
+
81
+ subparser = get_subparser(topic)
82
+ if subparser is None:
152
83
  print(f"Error: Unknown command '{topic}'", file=sys.stderr)
153
- print("Available commands: serve, config", file=sys.stderr)
154
- print()
155
- print("Use 'gitwebhooks-cli help' to see main help", file=sys.stderr)
84
+ return
85
+
86
+ print(subparser.format_help(), end='')
@@ -16,7 +16,7 @@ Validation Functions:
16
16
  - _get_platform_default_event: Returns platform-specific default event
17
17
 
18
18
  TECHNICAL DEBT:
19
- This module (768 lines) exceeds the Constitution's recommended 400 line limit.
19
+ This module (785 lines) exceeds the Constitution's recommended 400 line limit.
20
20
  This is acceptable because the module contains the complete interactive wizard
21
21
  logic including platform selection, repository configuration, event configuration,
22
22
  and file generation.
@@ -30,9 +30,15 @@ Refactoring Plan (future task):
30
30
  import os
31
31
  import re
32
32
  from dataclasses import dataclass
33
- from typing import Optional, List, Dict
33
+ from typing import Dict, Optional, Tuple
34
34
  import configparser
35
35
 
36
+ from gitwebhooks.utils.constants import (
37
+ CONFIG_PATH_LOCAL,
38
+ CONFIG_PATH_SYSTEM,
39
+ CONFIG_PATH_USER,
40
+ )
41
+
36
42
 
37
43
  # =============================================================================
38
44
  # Module Constants
@@ -40,15 +46,15 @@ import configparser
40
46
 
41
47
  CONFIG_LEVELS = {
42
48
  'system': {
43
- 'path': '/etc/gitwebhooks.ini',
49
+ 'path': CONFIG_PATH_SYSTEM,
44
50
  'requires_root': True
45
51
  },
46
52
  'local': {
47
- 'path': '/usr/local/etc/gitwebhooks.ini',
53
+ 'path': CONFIG_PATH_LOCAL,
48
54
  'requires_root': True
49
55
  },
50
56
  'user': {
51
- 'path': '~/.gitwebhooks.ini',
57
+ 'path': CONFIG_PATH_USER,
52
58
  'requires_root': False
53
59
  }
54
60
  }
@@ -201,7 +207,7 @@ def validate_port(value: str) -> bool:
201
207
  return False
202
208
 
203
209
 
204
- def validate_event_input(value: str) -> tuple[bool, str]:
210
+ def validate_event_input(value: str) -> Tuple[bool, str]:
205
211
  """
206
212
  验证事件名称输入。
207
213
 
@@ -760,7 +766,7 @@ class Wizard:
760
766
  print("后续步骤:")
761
767
  print(f" 1. 检查配置文件内容: gitwebhooks-cli config view {path}")
762
768
  print(f" 2. 启动服务器: gitwebhooks-cli -c {path}")
763
- print(" 3. 或安装为服务: sudo gitwebhooks-cli service install")
769
+ print(" 3. 或安装为服务: sudo gitwebhooks-cli serve install")
764
770
 
765
771
  def _cleanup(self) -> None:
766
772
  """
@@ -4,143 +4,19 @@ Provides functions for interactive user input and validation.
4
4
  """
5
5
 
6
6
  import sys
7
- from typing import Any, Callable, Optional, Dict, List, Tuple
8
7
 
9
- from gitwebhooks.utils.constants import ConfigLevel
8
+ from gitwebhooks.utils.constants import (
9
+ CONFIG_PATH_LOCAL,
10
+ CONFIG_PATH_SYSTEM,
11
+ CONFIG_PATH_USER,
12
+ ConfigLevel,
13
+ )
10
14
 
11
15
 
12
16
  # Question template for configuration initialization
13
- QUESTIONS = [
14
- # Server configuration
15
- {
16
- 'key': 'server_address',
17
- 'prompt': 'Server listen address [default: 0.0.0.0]: ',
18
- 'default': '0.0.0.0',
19
- 'validator': None,
20
- 'required': True
21
- },
22
- {
23
- 'key': 'server_port',
24
- 'prompt': 'Server port [default: 6789]: ',
25
- 'default': '6789',
26
- 'validator': 'validate_port',
27
- 'required': True
28
- },
29
- {
30
- 'key': 'log_file',
31
- 'prompt': 'Log file path [default: ~/.gitwebhook.log]: ',
32
- 'default': '~/.gitwebhook.log',
33
- 'validator': None,
34
- 'required': False
35
- },
36
- ]
37
17
 
38
18
 
39
19
  # Platform-specific questions
40
- PLATFORM_QUESTIONS = {
41
- 'github': [
42
- {
43
- 'section': 'github',
44
- 'key': 'verify',
45
- 'prompt': ' Enable signature verification? (Y/n) [default: Y]: ',
46
- 'default': 'true',
47
- 'validator': None,
48
- 'required': False
49
- },
50
- {
51
- 'section': 'github',
52
- 'key': 'secret',
53
- 'prompt': ' Webhook secret (leave empty to skip): ',
54
- 'default': '',
55
- 'validator': None,
56
- 'required': False
57
- },
58
- ],
59
- 'gitee': [
60
- {
61
- 'section': 'gitee',
62
- 'key': 'verify',
63
- 'prompt': ' Enable signature verification? (Y/n) [default: Y]: ',
64
- 'default': 'true',
65
- 'validator': None,
66
- 'required': False
67
- },
68
- {
69
- 'section': 'gitee',
70
- 'key': 'secret',
71
- 'prompt': ' Webhook secret (leave empty to skip): ',
72
- 'default': '',
73
- 'validator': None,
74
- 'required': False
75
- },
76
- ],
77
- 'gitlab': [
78
- {
79
- 'section': 'gitlab',
80
- 'key': 'verify',
81
- 'prompt': ' Enable token verification? (Y/n) [default: Y]: ',
82
- 'default': 'true',
83
- 'validator': None,
84
- 'required': False
85
- },
86
- {
87
- 'section': 'gitlab',
88
- 'key': 'secret',
89
- 'prompt': ' Webhook token (leave empty to skip): ',
90
- 'default': '',
91
- 'validator': None,
92
- 'required': False
93
- },
94
- ],
95
- }
96
-
97
-
98
- def ask_question(prompt: str,
99
- validator: Optional[Callable[[str], Any]] = None,
100
- default: Optional[Any] = None,
101
- required: bool = True) -> Any:
102
- """Ask a question with optional validation and default value
103
-
104
- Args:
105
- prompt: Question prompt to display
106
- validator: Optional validation function that raises ValueError on invalid input
107
- default: Optional default value if user provides empty input
108
- required: Whether a value is required (defaults to True)
109
-
110
- Returns:
111
- Validated user input or default value
112
-
113
- Raises:
114
- KeyboardInterrupt: If user confirms exit on Ctrl+C
115
- """
116
- while True:
117
- try:
118
- response = input(prompt).strip()
119
-
120
- # Use default if empty input provided
121
- if not response and default is not None:
122
- return default
123
-
124
- # Validate if validator provided
125
- if validator:
126
- if isinstance(validator, str):
127
- # Validator is a string name, look up function
128
- validator_func = globals().get(validator)
129
- if validator_func:
130
- return validator_func(response)
131
- else:
132
- return validator(response)
133
-
134
- return response
135
-
136
- except KeyboardInterrupt:
137
- # Ask for confirmation before exiting
138
- if ask_yes_no('Confirm exit? Unsaved changes will be lost (y/N) ', default=False):
139
- print('Configuration cancelled')
140
- sys.exit(0)
141
- except ValueError as e:
142
- print(f'Invalid input: {e}')
143
- print('Please try again')
144
20
 
145
21
 
146
22
  def ask_yes_no(prompt: str, default: bool = False) -> bool:
@@ -205,78 +81,6 @@ def validate_port(value: str) -> int:
205
81
  raise ValueError('Port must be a number')
206
82
 
207
83
 
208
- def validate_address(value: str) -> str:
209
- """Validate server address
210
-
211
- Args:
212
- value: String input to validate
213
-
214
- Returns:
215
- Validated address string
216
-
217
- Raises:
218
- ValueError: If address is empty
219
- """
220
- if not value:
221
- raise ValueError('Server address cannot be empty')
222
- return value
223
-
224
-
225
- def validate_path(value: str) -> str:
226
- """Validate file path
227
-
228
- Args:
229
- value: String input to validate
230
-
231
- Returns:
232
- Validated path string
233
-
234
- Raises:
235
- ValueError: If path is empty
236
- """
237
- if not value:
238
- raise ValueError('Path cannot be empty')
239
- return value
240
-
241
-
242
- def validate_bool(value: str) -> bool:
243
- """Validate boolean input
244
-
245
- Args:
246
- value: String input to validate
247
-
248
- Returns:
249
- True or False
250
-
251
- Raises:
252
- ValueError: If input is not a boolean value
253
- """
254
- value_lower = value.lower()
255
- if value_lower in ('y', 'yes', 'true', '1'):
256
- return True
257
- elif value_lower in ('n', 'no', 'false', '0'):
258
- return False
259
- else:
260
- raise ValueError('Please enter yes/no or y/n')
261
-
262
-
263
- def validate_text(value: str) -> str:
264
- """Validate text input (non-empty)
265
-
266
- Args:
267
- value: String input to validate
268
-
269
- Returns:
270
- Validated text string
271
-
272
- Raises:
273
- ValueError: If text is empty
274
- """
275
- if not value:
276
- raise ValueError('Input cannot be empty')
277
- return value
278
-
279
-
280
84
  def ask_config_level() -> ConfigLevel:
281
85
  """Ask user to select configuration file level
282
86
 
@@ -290,9 +94,9 @@ def ask_config_level() -> ConfigLevel:
290
94
  KeyboardInterrupt: If user confirms exit on Ctrl+C
291
95
  """
292
96
  print('Select configuration file level for the service:')
293
- print(' 1. User level (~/.gitwebhooks.ini) - Single user, highest priority')
294
- print(' 2. Local level (/usr/local/etc/gitwebhooks.ini) - Local system, medium priority')
295
- print(' 3. System level (/etc/gitwebhooks.ini) - Global system, lowest priority')
97
+ print(f' 1. User level ({CONFIG_PATH_USER}) - Single user, highest priority')
98
+ print(f' 2. Local level ({CONFIG_PATH_LOCAL}) - Local system, medium priority')
99
+ print(f' 3. System level ({CONFIG_PATH_SYSTEM}) - Global system, lowest priority')
296
100
  print()
297
101
 
298
102
  choice_map = {