janito 2.2.0__py3-none-any.whl → 2.3.0__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 (130) hide show
  1. janito/__init__.py +6 -6
  2. janito/agent/setup_agent.py +14 -5
  3. janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +3 -1
  4. janito/cli/chat_mode/bindings.py +6 -0
  5. janito/cli/chat_mode/session.py +16 -0
  6. janito/cli/chat_mode/shell/autocomplete.py +21 -21
  7. janito/cli/chat_mode/shell/commands/__init__.py +3 -0
  8. janito/cli/chat_mode/shell/commands/clear.py +12 -12
  9. janito/cli/chat_mode/shell/commands/exec.py +27 -0
  10. janito/cli/chat_mode/shell/commands/multi.py +51 -51
  11. janito/cli/chat_mode/shell/commands/tools.py +17 -6
  12. janito/cli/chat_mode/shell/input_history.py +62 -62
  13. janito/cli/chat_mode/shell/session/manager.py +1 -0
  14. janito/cli/chat_mode/toolbar.py +1 -0
  15. janito/cli/cli_commands/list_models.py +35 -35
  16. janito/cli/cli_commands/list_providers.py +9 -9
  17. janito/cli/cli_commands/list_tools.py +53 -53
  18. janito/cli/cli_commands/model_selection.py +50 -50
  19. janito/cli/cli_commands/model_utils.py +13 -2
  20. janito/cli/cli_commands/set_api_key.py +19 -19
  21. janito/cli/cli_commands/show_config.py +51 -51
  22. janito/cli/cli_commands/show_system_prompt.py +62 -62
  23. janito/cli/config.py +2 -1
  24. janito/cli/core/__init__.py +4 -4
  25. janito/cli/core/event_logger.py +59 -59
  26. janito/cli/core/getters.py +3 -1
  27. janito/cli/core/runner.py +165 -148
  28. janito/cli/core/setters.py +5 -1
  29. janito/cli/core/unsetters.py +54 -54
  30. janito/cli/main_cli.py +12 -1
  31. janito/cli/prompt_core.py +5 -2
  32. janito/cli/rich_terminal_reporter.py +22 -3
  33. janito/cli/single_shot_mode/__init__.py +6 -6
  34. janito/cli/single_shot_mode/handler.py +11 -1
  35. janito/cli/verbose_output.py +1 -1
  36. janito/config.py +5 -5
  37. janito/config_manager.py +2 -0
  38. janito/driver_events.py +14 -0
  39. janito/drivers/anthropic/driver.py +113 -113
  40. janito/drivers/azure_openai/driver.py +38 -3
  41. janito/drivers/driver_registry.py +0 -2
  42. janito/drivers/openai/driver.py +196 -36
  43. janito/formatting_token.py +54 -54
  44. janito/i18n/__init__.py +35 -35
  45. janito/i18n/messages.py +23 -23
  46. janito/i18n/pt.py +47 -47
  47. janito/llm/__init__.py +5 -5
  48. janito/llm/agent.py +443 -443
  49. janito/llm/auth.py +1 -0
  50. janito/llm/driver.py +7 -1
  51. janito/llm/driver_config.py +1 -0
  52. janito/llm/driver_config_builder.py +34 -34
  53. janito/llm/driver_input.py +12 -12
  54. janito/llm/message_parts.py +60 -60
  55. janito/llm/model.py +38 -38
  56. janito/llm/provider.py +196 -196
  57. janito/provider_config.py +7 -3
  58. janito/provider_registry.py +176 -158
  59. janito/providers/__init__.py +1 -0
  60. janito/providers/anthropic/model_info.py +22 -22
  61. janito/providers/anthropic/provider.py +2 -2
  62. janito/providers/azure_openai/model_info.py +7 -6
  63. janito/providers/azure_openai/provider.py +30 -2
  64. janito/providers/deepseek/__init__.py +1 -1
  65. janito/providers/deepseek/model_info.py +16 -16
  66. janito/providers/deepseek/provider.py +91 -91
  67. janito/providers/google/model_info.py +21 -29
  68. janito/providers/google/provider.py +49 -38
  69. janito/providers/mistralai/provider.py +2 -2
  70. janito/providers/provider_static_info.py +2 -3
  71. janito/tools/adapters/__init__.py +1 -1
  72. janito/tools/adapters/local/adapter.py +33 -11
  73. janito/tools/adapters/local/ask_user.py +102 -102
  74. janito/tools/adapters/local/copy_file.py +84 -84
  75. janito/tools/adapters/local/create_directory.py +69 -69
  76. janito/tools/adapters/local/create_file.py +82 -82
  77. janito/tools/adapters/local/delete_text_in_file.py +4 -7
  78. janito/tools/adapters/local/fetch_url.py +97 -97
  79. janito/tools/adapters/local/find_files.py +138 -138
  80. janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
  81. janito/tools/adapters/local/get_file_outline/core.py +117 -117
  82. janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
  83. janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
  84. janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
  85. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
  86. janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
  87. janito/tools/adapters/local/move_file.py +3 -13
  88. janito/tools/adapters/local/python_code_run.py +166 -166
  89. janito/tools/adapters/local/python_command_run.py +164 -164
  90. janito/tools/adapters/local/python_file_run.py +163 -163
  91. janito/tools/adapters/local/remove_directory.py +6 -17
  92. janito/tools/adapters/local/remove_file.py +4 -10
  93. janito/tools/adapters/local/replace_text_in_file.py +6 -9
  94. janito/tools/adapters/local/run_bash_command.py +176 -176
  95. janito/tools/adapters/local/run_powershell_command.py +219 -219
  96. janito/tools/adapters/local/search_text/__init__.py +1 -1
  97. janito/tools/adapters/local/search_text/core.py +201 -201
  98. janito/tools/adapters/local/search_text/match_lines.py +1 -1
  99. janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
  100. janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
  101. janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
  102. janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
  103. janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
  104. janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
  105. janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
  106. janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
  107. janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
  108. janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
  109. janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
  110. janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
  111. janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
  112. janito/tools/adapters/local/view_file.py +167 -167
  113. janito/tools/inspect_registry.py +17 -17
  114. janito/tools/tool_base.py +105 -105
  115. janito/tools/tool_events.py +58 -58
  116. janito/tools/tool_run_exception.py +12 -12
  117. janito/tools/tool_use_tracker.py +81 -81
  118. janito/tools/tool_utils.py +45 -45
  119. janito/tools/tools_adapter.py +78 -6
  120. janito/tools/tools_schema.py +104 -104
  121. janito/version.py +4 -4
  122. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/METADATA +388 -251
  123. janito-2.3.0.dist-info/RECORD +181 -0
  124. janito/drivers/google_genai/driver.py +0 -54
  125. janito/drivers/google_genai/schema_generator.py +0 -67
  126. janito-2.2.0.dist-info/RECORD +0 -182
  127. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/WHEEL +0 -0
  128. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/entry_points.txt +0 -0
  129. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/licenses/LICENSE +0 -0
  130. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/top_level.txt +0 -0
@@ -1,145 +1,145 @@
1
- import os
2
- from janito.gitignore_utils import GitignoreFilter
3
- from .match_lines import match_line, should_limit, read_file_lines
4
-
5
-
6
- def walk_directory(search_path, max_depth):
7
- if max_depth == 1:
8
- walk_result = next(os.walk(search_path), None)
9
- if walk_result is None:
10
- return [(search_path, [], [])]
11
- else:
12
- return [walk_result]
13
- else:
14
- return os.walk(search_path)
15
-
16
-
17
- def filter_dirs(dirs, root, gitignore_filter):
18
- # Always exclude directories named .git, regardless of gitignore
19
- return [
20
- d
21
- for d in dirs
22
- if d != ".git" and not gitignore_filter.is_ignored(os.path.join(root, d))
23
- ]
24
-
25
-
26
- def process_file_count_only(
27
- file_path,
28
- per_file_counts,
29
- pattern,
30
- regex,
31
- use_regex,
32
- case_sensitive,
33
- max_results,
34
- total_results,
35
- ):
36
- match_count, file_limit_reached, _ = read_file_lines(
37
- file_path,
38
- pattern,
39
- regex,
40
- use_regex,
41
- case_sensitive,
42
- True,
43
- max_results,
44
- total_results + sum(count for _, count in per_file_counts),
45
- )
46
- if match_count > 0:
47
- per_file_counts.append((file_path, match_count))
48
- return file_limit_reached
49
-
50
-
51
- def process_file_collect(
52
- file_path,
53
- dir_output,
54
- per_file_counts,
55
- pattern,
56
- regex,
57
- use_regex,
58
- case_sensitive,
59
- max_results,
60
- total_results,
61
- ):
62
- actual_match_count, file_limit_reached, file_lines_output = read_file_lines(
63
- file_path,
64
- pattern,
65
- regex,
66
- use_regex,
67
- case_sensitive,
68
- False,
69
- max_results,
70
- total_results + len(dir_output),
71
- )
72
- dir_output.extend(file_lines_output)
73
- if actual_match_count > 0:
74
- per_file_counts.append((file_path, actual_match_count))
75
- return file_limit_reached
76
-
77
-
78
- def should_limit_depth(root, search_path, max_depth, dirs):
79
- if max_depth > 0:
80
- rel_root = os.path.relpath(root, search_path)
81
- if rel_root != ".":
82
- depth = rel_root.count(os.sep) + 1
83
- if depth >= max_depth:
84
- del dirs[:]
85
-
86
-
87
- def traverse_directory(
88
- search_path,
89
- pattern,
90
- regex,
91
- use_regex,
92
- case_sensitive,
93
- max_depth,
94
- max_results,
95
- total_results,
96
- count_only,
97
- ):
98
- dir_output = []
99
- dir_limit_reached = False
100
- per_file_counts = []
101
- walker = walk_directory(search_path, max_depth)
102
- gitignore_filter = GitignoreFilter(search_path)
103
-
104
- for root, dirs, files in walker:
105
- dirs[:] = filter_dirs(dirs, root, gitignore_filter)
106
- for file in files:
107
- file_path = os.path.join(root, file)
108
- if gitignore_filter.is_ignored(file_path):
109
- continue
110
- if count_only:
111
- file_limit_reached = process_file_count_only(
112
- file_path,
113
- per_file_counts,
114
- pattern,
115
- regex,
116
- use_regex,
117
- case_sensitive,
118
- max_results,
119
- total_results,
120
- )
121
- if file_limit_reached:
122
- dir_limit_reached = True
123
- break
124
- else:
125
- file_limit_reached = process_file_collect(
126
- file_path,
127
- dir_output,
128
- per_file_counts,
129
- pattern,
130
- regex,
131
- use_regex,
132
- case_sensitive,
133
- max_results,
134
- total_results,
135
- )
136
- if file_limit_reached:
137
- dir_limit_reached = True
138
- break
139
- if dir_limit_reached:
140
- break
141
- should_limit_depth(root, search_path, max_depth, dirs)
142
- if count_only:
143
- return per_file_counts, dir_limit_reached, []
144
- else:
145
- return dir_output, dir_limit_reached, per_file_counts
1
+ import os
2
+ from janito.gitignore_utils import GitignoreFilter
3
+ from .match_lines import match_line, should_limit, read_file_lines
4
+
5
+
6
+ def walk_directory(search_path, max_depth):
7
+ if max_depth == 1:
8
+ walk_result = next(os.walk(search_path), None)
9
+ if walk_result is None:
10
+ return [(search_path, [], [])]
11
+ else:
12
+ return [walk_result]
13
+ else:
14
+ return os.walk(search_path)
15
+
16
+
17
+ def filter_dirs(dirs, root, gitignore_filter):
18
+ # Always exclude directories named .git, regardless of gitignore
19
+ return [
20
+ d
21
+ for d in dirs
22
+ if d != ".git" and not gitignore_filter.is_ignored(os.path.join(root, d))
23
+ ]
24
+
25
+
26
+ def process_file_count_only(
27
+ file_path,
28
+ per_file_counts,
29
+ pattern,
30
+ regex,
31
+ use_regex,
32
+ case_sensitive,
33
+ max_results,
34
+ total_results,
35
+ ):
36
+ match_count, file_limit_reached, _ = read_file_lines(
37
+ file_path,
38
+ pattern,
39
+ regex,
40
+ use_regex,
41
+ case_sensitive,
42
+ True,
43
+ max_results,
44
+ total_results + sum(count for _, count in per_file_counts),
45
+ )
46
+ if match_count > 0:
47
+ per_file_counts.append((file_path, match_count))
48
+ return file_limit_reached
49
+
50
+
51
+ def process_file_collect(
52
+ file_path,
53
+ dir_output,
54
+ per_file_counts,
55
+ pattern,
56
+ regex,
57
+ use_regex,
58
+ case_sensitive,
59
+ max_results,
60
+ total_results,
61
+ ):
62
+ actual_match_count, file_limit_reached, file_lines_output = read_file_lines(
63
+ file_path,
64
+ pattern,
65
+ regex,
66
+ use_regex,
67
+ case_sensitive,
68
+ False,
69
+ max_results,
70
+ total_results + len(dir_output),
71
+ )
72
+ dir_output.extend(file_lines_output)
73
+ if actual_match_count > 0:
74
+ per_file_counts.append((file_path, actual_match_count))
75
+ return file_limit_reached
76
+
77
+
78
+ def should_limit_depth(root, search_path, max_depth, dirs):
79
+ if max_depth > 0:
80
+ rel_root = os.path.relpath(root, search_path)
81
+ if rel_root != ".":
82
+ depth = rel_root.count(os.sep) + 1
83
+ if depth >= max_depth:
84
+ del dirs[:]
85
+
86
+
87
+ def traverse_directory(
88
+ search_path,
89
+ pattern,
90
+ regex,
91
+ use_regex,
92
+ case_sensitive,
93
+ max_depth,
94
+ max_results,
95
+ total_results,
96
+ count_only,
97
+ ):
98
+ dir_output = []
99
+ dir_limit_reached = False
100
+ per_file_counts = []
101
+ walker = walk_directory(search_path, max_depth)
102
+ gitignore_filter = GitignoreFilter(search_path)
103
+
104
+ for root, dirs, files in walker:
105
+ dirs[:] = filter_dirs(dirs, root, gitignore_filter)
106
+ for file in files:
107
+ file_path = os.path.join(root, file)
108
+ if gitignore_filter.is_ignored(file_path):
109
+ continue
110
+ if count_only:
111
+ file_limit_reached = process_file_count_only(
112
+ file_path,
113
+ per_file_counts,
114
+ pattern,
115
+ regex,
116
+ use_regex,
117
+ case_sensitive,
118
+ max_results,
119
+ total_results,
120
+ )
121
+ if file_limit_reached:
122
+ dir_limit_reached = True
123
+ break
124
+ else:
125
+ file_limit_reached = process_file_collect(
126
+ file_path,
127
+ dir_output,
128
+ per_file_counts,
129
+ pattern,
130
+ regex,
131
+ use_regex,
132
+ case_sensitive,
133
+ max_results,
134
+ total_results,
135
+ )
136
+ if file_limit_reached:
137
+ dir_limit_reached = True
138
+ break
139
+ if dir_limit_reached:
140
+ break
141
+ should_limit_depth(root, search_path, max_depth, dirs)
142
+ if count_only:
143
+ return per_file_counts, dir_limit_reached, []
144
+ else:
145
+ return dir_output, dir_limit_reached, per_file_counts
@@ -1 +1 @@
1
- # Validation syntax package
1
+ # Validation syntax package
@@ -1,106 +1,106 @@
1
- import os
2
- from janito.i18n import tr
3
- from janito.tools.tool_base import ToolBase
4
- from janito.report_events import ReportAction
5
- from janito.tools.adapters.local.adapter import register_local_tool
6
- from janito.tools.tool_utils import display_path
7
- from janito.tools.adapters.local.adapter import register_local_tool as register_tool
8
-
9
- from .python_validator import validate_python
10
- from .json_validator import validate_json
11
- from .yaml_validator import validate_yaml
12
- from .ps1_validator import validate_ps1
13
- from .xml_validator import validate_xml
14
- from .html_validator import validate_html
15
- from .markdown_validator import validate_markdown
16
- from .js_validator import validate_js
17
- from .css_validator import validate_css
18
-
19
-
20
- def _get_validator(ext):
21
- """Return the appropriate validator function for the file extension."""
22
- mapping = {
23
- ".py": validate_python,
24
- ".pyw": validate_python,
25
- ".json": validate_json,
26
- ".yml": validate_yaml,
27
- ".yaml": validate_yaml,
28
- ".ps1": validate_ps1,
29
- ".xml": validate_xml,
30
- ".html": validate_html,
31
- ".htm": validate_html,
32
- ".md": validate_markdown,
33
- ".js": validate_js,
34
- ".css": validate_css,
35
- }
36
- return mapping.get(ext)
37
-
38
-
39
- def _handle_validation_error(e, report_warning):
40
- msg = tr("⚠️ Warning: Syntax error: {error}", error=e)
41
- if report_warning:
42
- report_warning(msg)
43
- return msg
44
-
45
-
46
- def validate_file_syntax(
47
- file_path: str, report_info=None, report_warning=None, report_success=None
48
- ) -> str:
49
- ext = os.path.splitext(file_path)[1].lower()
50
- validator = _get_validator(ext)
51
- try:
52
- if validator:
53
- return validator(file_path)
54
- else:
55
- msg = tr("⚠️ Warning: Unsupported file extension: {ext}", ext=ext)
56
- if report_warning:
57
- report_warning(msg)
58
- return msg
59
- except Exception as e:
60
- return _handle_validation_error(e, report_warning)
61
-
62
-
63
- class ValidateFileSyntaxTool(ToolBase):
64
- """
65
- Validate a file for syntax issues.
66
-
67
- Supported types:
68
- - Python (.py, .pyw)
69
- - JSON (.json)
70
- - YAML (.yml, .yaml)
71
- - PowerShell (.ps1)
72
- - XML (.xml)
73
- - HTML (.html, .htm) [lxml]
74
- - Markdown (.md)
75
- - JavaScript (.js)
76
-
77
- Args:
78
- file_path (str): Path to the file to validate.
79
- Returns:
80
- str: Validation status message. Example:
81
- - "✅ Syntax OK"
82
- - "⚠️ Warning: Syntax error: <error message>"
83
- - "⚠️ Warning: Unsupported file extension: <ext>"
84
- """
85
-
86
- tool_name = "validate_file_syntax"
87
-
88
- def run(self, file_path: str) -> str:
89
- disp_path = display_path(file_path)
90
- self.report_action(
91
- tr(
92
- "🔎 Validate syntax for file '{disp_path}' ...",
93
- disp_path=disp_path,
94
- ),
95
- ReportAction.READ,
96
- )
97
- result = validate_file_syntax(
98
- file_path,
99
- report_info=self.report_info,
100
- report_warning=self.report_warning,
101
- report_success=self.report_success,
102
- )
103
- if result.startswith("✅"):
104
- self.report_success(result, ReportAction.READ)
105
-
106
- return result
1
+ import os
2
+ from janito.i18n import tr
3
+ from janito.tools.tool_base import ToolBase
4
+ from janito.report_events import ReportAction
5
+ from janito.tools.adapters.local.adapter import register_local_tool
6
+ from janito.tools.tool_utils import display_path
7
+ from janito.tools.adapters.local.adapter import register_local_tool as register_tool
8
+
9
+ from .python_validator import validate_python
10
+ from .json_validator import validate_json
11
+ from .yaml_validator import validate_yaml
12
+ from .ps1_validator import validate_ps1
13
+ from .xml_validator import validate_xml
14
+ from .html_validator import validate_html
15
+ from .markdown_validator import validate_markdown
16
+ from .js_validator import validate_js
17
+ from .css_validator import validate_css
18
+
19
+
20
+ def _get_validator(ext):
21
+ """Return the appropriate validator function for the file extension."""
22
+ mapping = {
23
+ ".py": validate_python,
24
+ ".pyw": validate_python,
25
+ ".json": validate_json,
26
+ ".yml": validate_yaml,
27
+ ".yaml": validate_yaml,
28
+ ".ps1": validate_ps1,
29
+ ".xml": validate_xml,
30
+ ".html": validate_html,
31
+ ".htm": validate_html,
32
+ ".md": validate_markdown,
33
+ ".js": validate_js,
34
+ ".css": validate_css,
35
+ }
36
+ return mapping.get(ext)
37
+
38
+
39
+ def _handle_validation_error(e, report_warning):
40
+ msg = tr("⚠️ Warning: Syntax error: {error}", error=e)
41
+ if report_warning:
42
+ report_warning(msg)
43
+ return msg
44
+
45
+
46
+ def validate_file_syntax(
47
+ file_path: str, report_info=None, report_warning=None, report_success=None
48
+ ) -> str:
49
+ ext = os.path.splitext(file_path)[1].lower()
50
+ validator = _get_validator(ext)
51
+ try:
52
+ if validator:
53
+ return validator(file_path)
54
+ else:
55
+ msg = tr("⚠️ Warning: Unsupported file extension: {ext}", ext=ext)
56
+ if report_warning:
57
+ report_warning(msg)
58
+ return msg
59
+ except Exception as e:
60
+ return _handle_validation_error(e, report_warning)
61
+
62
+
63
+ class ValidateFileSyntaxTool(ToolBase):
64
+ """
65
+ Validate a file for syntax issues.
66
+
67
+ Supported types:
68
+ - Python (.py, .pyw)
69
+ - JSON (.json)
70
+ - YAML (.yml, .yaml)
71
+ - PowerShell (.ps1)
72
+ - XML (.xml)
73
+ - HTML (.html, .htm) [lxml]
74
+ - Markdown (.md)
75
+ - JavaScript (.js)
76
+
77
+ Args:
78
+ file_path (str): Path to the file to validate.
79
+ Returns:
80
+ str: Validation status message. Example:
81
+ - "✅ Syntax OK"
82
+ - "⚠️ Warning: Syntax error: <error message>"
83
+ - "⚠️ Warning: Unsupported file extension: <ext>"
84
+ """
85
+
86
+ tool_name = "validate_file_syntax"
87
+
88
+ def run(self, file_path: str) -> str:
89
+ disp_path = display_path(file_path)
90
+ self.report_action(
91
+ tr(
92
+ "🔎 Validate syntax for file '{disp_path}' ...",
93
+ disp_path=disp_path,
94
+ ),
95
+ ReportAction.READ,
96
+ )
97
+ result = validate_file_syntax(
98
+ file_path,
99
+ report_info=self.report_info,
100
+ report_warning=self.report_warning,
101
+ report_success=self.report_success,
102
+ )
103
+ if result.startswith("✅"):
104
+ self.report_success(result, ReportAction.READ)
105
+
106
+ return result
@@ -1,35 +1,35 @@
1
- from janito.i18n import tr
2
- import re
3
-
4
-
5
- def validate_css(file_path: str) -> str:
6
- with open(file_path, "r", encoding="utf-8") as f:
7
- content = f.read()
8
- errors = []
9
- # Check for unmatched curly braces
10
- if content.count("{") != content.count("}"):
11
- errors.append("Unmatched curly braces { }")
12
- # Check for unclosed comments
13
- if content.count("/*") != content.count("*/"):
14
- errors.append("Unclosed comment (/* ... */)")
15
- # Check for invalid property declarations (very basic)
16
- for i, line in enumerate(content.splitlines(), 1):
17
- # Ignore empty lines and comments
18
- if not line.strip() or line.strip().startswith("/*"):
19
- continue
20
- # Match property: value; (allow whitespace)
21
- if ":" in line and not re.search(r":.*;", line):
22
- errors.append(
23
- f"Line {i}: Missing semicolon after property value | {line.strip()}"
24
- )
25
- # Match lines with property but missing colon
26
- if ";" in line and ":" not in line:
27
- errors.append(
28
- f"Line {i}: Missing colon in property declaration | {line.strip()}"
29
- )
30
- if errors:
31
- msg = tr(
32
- "⚠️ Warning: CSS syntax issues found:\n{errors}", errors="\n".join(errors)
33
- )
34
- return msg
35
- return "✅ OK"
1
+ from janito.i18n import tr
2
+ import re
3
+
4
+
5
+ def validate_css(file_path: str) -> str:
6
+ with open(file_path, "r", encoding="utf-8") as f:
7
+ content = f.read()
8
+ errors = []
9
+ # Check for unmatched curly braces
10
+ if content.count("{") != content.count("}"):
11
+ errors.append("Unmatched curly braces { }")
12
+ # Check for unclosed comments
13
+ if content.count("/*") != content.count("*/"):
14
+ errors.append("Unclosed comment (/* ... */)")
15
+ # Check for invalid property declarations (very basic)
16
+ for i, line in enumerate(content.splitlines(), 1):
17
+ # Ignore empty lines and comments
18
+ if not line.strip() or line.strip().startswith("/*"):
19
+ continue
20
+ # Match property: value; (allow whitespace)
21
+ if ":" in line and not re.search(r":.*;", line):
22
+ errors.append(
23
+ f"Line {i}: Missing semicolon after property value | {line.strip()}"
24
+ )
25
+ # Match lines with property but missing colon
26
+ if ";" in line and ":" not in line:
27
+ errors.append(
28
+ f"Line {i}: Missing colon in property declaration | {line.strip()}"
29
+ )
30
+ if errors:
31
+ msg = tr(
32
+ "⚠️ Warning: CSS syntax issues found:\n{errors}", errors="\n".join(errors)
33
+ )
34
+ return msg
35
+ return "✅ OK"