troubadix 25.7.2__py3-none-any.whl → 25.7.3__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.
- troubadix/__version__.py +1 -1
- troubadix/helper/if_block_parser.py +113 -63
- troubadix/plugins/__init__.py +2 -0
- troubadix/plugins/if_statement_syntax.py +41 -0
- troubadix/plugins/using_display.py +6 -13
- {troubadix-25.7.2.dist-info → troubadix-25.7.3.dist-info}/METADATA +1 -1
- {troubadix-25.7.2.dist-info → troubadix-25.7.3.dist-info}/RECORD +10 -9
- {troubadix-25.7.2.dist-info → troubadix-25.7.3.dist-info}/LICENSE +0 -0
- {troubadix-25.7.2.dist-info → troubadix-25.7.3.dist-info}/WHEEL +0 -0
- {troubadix-25.7.2.dist-info → troubadix-25.7.3.dist-info}/entry_points.txt +0 -0
troubadix/__version__.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"""Helper for parsing if blocks and single-expression if statements in NASL files."""
|
|
5
5
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
7
8
|
|
|
8
9
|
from troubadix.helper.text_utils import (
|
|
9
10
|
StringState,
|
|
@@ -12,7 +13,7 @@ from troubadix.helper.text_utils import (
|
|
|
12
13
|
|
|
13
14
|
# Brace pairings
|
|
14
15
|
CONDITION_BRACES = ("(", ")")
|
|
15
|
-
|
|
16
|
+
BODY_BRACES = ("{", "}")
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@dataclass
|
|
@@ -21,10 +22,35 @@ class IfStatement:
|
|
|
21
22
|
if_end: int
|
|
22
23
|
condition_start: int
|
|
23
24
|
condition_end: int
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
outcome_start: int
|
|
26
|
+
outcome_end: int
|
|
26
27
|
condition: str
|
|
27
|
-
|
|
28
|
+
outcome: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class IfErrorType(Enum):
|
|
32
|
+
UNCLOSED_CONDITION = "Unclosed parenthesis in if condition at line {line}"
|
|
33
|
+
UNCLOSED_BODY = "Unclosed brace in if body at line {line}"
|
|
34
|
+
MISSING_OUTCOME = (
|
|
35
|
+
"Missing statement or body after if condition at line {line}"
|
|
36
|
+
)
|
|
37
|
+
TERMINATED_AFTER_CONDITION = (
|
|
38
|
+
"Semicolon after if condition at line {line} causes if to terminate early. "
|
|
39
|
+
"Following block will always execute."
|
|
40
|
+
)
|
|
41
|
+
MISSING_STATEMENT = "Missing expression after if condition at line {line}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class IfParseError:
|
|
46
|
+
line: int
|
|
47
|
+
error_type: IfErrorType
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class IfParseResult:
|
|
52
|
+
statements: list[IfStatement]
|
|
53
|
+
errors: list[IfParseError]
|
|
28
54
|
|
|
29
55
|
|
|
30
56
|
class IfParser:
|
|
@@ -33,44 +59,76 @@ class IfParser:
|
|
|
33
59
|
def __init__(self, file_content: str):
|
|
34
60
|
self.file_content = file_content
|
|
35
61
|
|
|
36
|
-
def find_if_statements(self) ->
|
|
37
|
-
"""
|
|
62
|
+
def find_if_statements(self) -> IfParseResult:
|
|
63
|
+
"""
|
|
64
|
+
Parse the file to find all if statements (blocks and single expressions), collecting errors.
|
|
65
|
+
|
|
66
|
+
Example NASL if statements:
|
|
67
|
+
if (x > 0) {
|
|
68
|
+
foo();
|
|
69
|
+
}
|
|
70
|
+
if (y < 5)
|
|
71
|
+
bar();
|
|
72
|
+
"""
|
|
38
73
|
results: list[IfStatement] = []
|
|
74
|
+
errors: list[IfParseError] = []
|
|
75
|
+
# Step 1: Find all 'if' condition starts
|
|
39
76
|
starts = self._find_condition_starts()
|
|
40
77
|
if not starts:
|
|
41
|
-
return results
|
|
78
|
+
return IfParseResult(results, errors)
|
|
42
79
|
|
|
43
80
|
for if_start, opening_brace in starts:
|
|
44
81
|
line, _ = index_to_linecol(self.file_content, if_start)
|
|
45
82
|
|
|
46
|
-
|
|
47
|
-
|
|
83
|
+
# Step 2: Find the end of the condition (the closing parenthesis)
|
|
84
|
+
condition_end, condition_error = self._find_closing_brace(
|
|
85
|
+
opening_brace, CONDITION_BRACES
|
|
48
86
|
)
|
|
87
|
+
if condition_error:
|
|
88
|
+
errors.append(
|
|
89
|
+
IfParseError(line=line, error_type=condition_error)
|
|
90
|
+
)
|
|
91
|
+
continue
|
|
49
92
|
condition = self.file_content[
|
|
50
93
|
opening_brace + 1 : condition_end
|
|
51
94
|
].strip()
|
|
52
|
-
statement_pos = self._find_statement_start(condition_end, line)
|
|
53
95
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
96
|
+
# Step 3: Find the start of the outcome (first non-whitespace after condition)
|
|
97
|
+
outcome_start, outcome_error = self._find_outcome_start(
|
|
98
|
+
condition_end
|
|
99
|
+
)
|
|
100
|
+
if outcome_error:
|
|
101
|
+
errors.append(IfParseError(line=line, error_type=outcome_error))
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Step 4: Determine if this is a body or single-expression statement
|
|
105
|
+
if self.file_content[outcome_start] == "{":
|
|
106
|
+
# Body: find closing brace for body '}'
|
|
107
|
+
body_end, body_error = self._find_closing_brace(
|
|
108
|
+
outcome_start, BODY_BRACES
|
|
58
109
|
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
110
|
+
if body_error:
|
|
111
|
+
errors.append(
|
|
112
|
+
IfParseError(line=line, error_type=body_error)
|
|
113
|
+
)
|
|
114
|
+
continue
|
|
115
|
+
if_end = body_end + 1
|
|
116
|
+
outcome_start = outcome_start + 1 # exclude opening brace
|
|
117
|
+
outcome_end = body_end
|
|
65
118
|
else:
|
|
66
|
-
# Single
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
119
|
+
# Single statement: find end of statement ';'
|
|
120
|
+
statement_end, statement_error = self._find_statement_end(
|
|
121
|
+
outcome_start
|
|
122
|
+
)
|
|
123
|
+
if statement_error:
|
|
124
|
+
errors.append(
|
|
125
|
+
IfParseError(line=line, error_type=statement_error)
|
|
126
|
+
)
|
|
127
|
+
continue
|
|
128
|
+
if_end = statement_end + 1
|
|
129
|
+
outcome_end = statement_end
|
|
130
|
+
|
|
131
|
+
outcome = self.file_content[outcome_start:outcome_end].strip()
|
|
74
132
|
|
|
75
133
|
results.append(
|
|
76
134
|
IfStatement(
|
|
@@ -78,21 +136,20 @@ class IfParser:
|
|
|
78
136
|
if_end=if_end,
|
|
79
137
|
condition_start=opening_brace + 1,
|
|
80
138
|
condition_end=condition_end,
|
|
81
|
-
|
|
82
|
-
|
|
139
|
+
outcome_start=outcome_start,
|
|
140
|
+
outcome_end=outcome_end,
|
|
83
141
|
condition=condition,
|
|
84
|
-
|
|
142
|
+
outcome=outcome,
|
|
85
143
|
)
|
|
86
144
|
)
|
|
87
145
|
|
|
88
|
-
return results
|
|
146
|
+
return IfParseResult(results, errors)
|
|
89
147
|
|
|
90
148
|
def _find_closing_brace(
|
|
91
149
|
self,
|
|
92
150
|
start_pos: int,
|
|
93
151
|
brace_pair: tuple[str, str],
|
|
94
|
-
|
|
95
|
-
) -> int:
|
|
152
|
+
) -> tuple[int | None, IfErrorType | None]:
|
|
96
153
|
"""Find the matching closing brace, with proper error reporting."""
|
|
97
154
|
opening_brace, closing_brace = brace_pair
|
|
98
155
|
open_count = 1
|
|
@@ -111,19 +168,13 @@ class IfParser:
|
|
|
111
168
|
elif char == closing_brace:
|
|
112
169
|
open_count -= 1
|
|
113
170
|
if open_count == 0:
|
|
114
|
-
return i
|
|
171
|
+
return i, None
|
|
115
172
|
|
|
116
|
-
#
|
|
173
|
+
# Error: unclosed brace
|
|
117
174
|
if opening_brace == "(":
|
|
118
|
-
|
|
119
|
-
f"Unclosed parenthesis in if statement at line {line}"
|
|
120
|
-
)
|
|
121
|
-
elif opening_brace == "{":
|
|
122
|
-
raise ValueError(f"Unclosed brace in if statement at line {line}")
|
|
175
|
+
return None, IfErrorType.UNCLOSED_CONDITION
|
|
123
176
|
else:
|
|
124
|
-
|
|
125
|
-
f"Unclosed {opening_brace} in if statement at line {line}"
|
|
126
|
-
)
|
|
177
|
+
return None, IfErrorType.UNCLOSED_BODY
|
|
127
178
|
|
|
128
179
|
def _find_condition_starts(self) -> list[tuple[int, int]]:
|
|
129
180
|
"""
|
|
@@ -159,42 +210,41 @@ class IfParser:
|
|
|
159
210
|
|
|
160
211
|
return starts
|
|
161
212
|
|
|
162
|
-
def
|
|
163
|
-
|
|
213
|
+
def _find_outcome_start(
|
|
214
|
+
self, condition_end: int
|
|
215
|
+
) -> tuple[int | None, IfErrorType | None]:
|
|
216
|
+
"""
|
|
217
|
+
Find the start of the outcome/then part after the condition (next non-whitespace character).
|
|
218
|
+
"""
|
|
164
219
|
pos = condition_end + 1
|
|
165
220
|
while pos < len(self.file_content) and self.file_content[pos].isspace():
|
|
166
221
|
pos += 1
|
|
167
222
|
|
|
168
223
|
if pos >= len(self.file_content):
|
|
169
|
-
|
|
170
|
-
f"Missing statement after if condition at line {line}"
|
|
171
|
-
)
|
|
224
|
+
return None, IfErrorType.MISSING_OUTCOME
|
|
172
225
|
|
|
173
226
|
if self.file_content[pos] == ";":
|
|
174
|
-
|
|
175
|
-
f"Semicolon after if condition at line {line} makes following block "
|
|
176
|
-
f"always execute. Remove semicolon to fix."
|
|
177
|
-
)
|
|
227
|
+
return None, IfErrorType.TERMINATED_AFTER_CONDITION
|
|
178
228
|
|
|
179
|
-
return pos
|
|
229
|
+
return pos, None
|
|
180
230
|
|
|
181
|
-
def
|
|
182
|
-
|
|
231
|
+
def _find_statement_end(
|
|
232
|
+
self, statement_start: int
|
|
233
|
+
) -> tuple[int | None, IfErrorType | None]:
|
|
234
|
+
"""Find the end of a single statement (semicolon outside of strings)."""
|
|
183
235
|
string_state = StringState()
|
|
184
236
|
|
|
185
|
-
for i in range(
|
|
237
|
+
for i in range(statement_start, len(self.file_content)):
|
|
186
238
|
char = self.file_content[i]
|
|
187
239
|
string_state.process_next_char(char)
|
|
188
240
|
if not string_state.in_string and char == ";":
|
|
189
|
-
return i
|
|
241
|
+
return i, None
|
|
190
242
|
|
|
191
|
-
|
|
192
|
-
f"Missing expression after if condition at line {line}"
|
|
193
|
-
)
|
|
243
|
+
return None, IfErrorType.MISSING_STATEMENT
|
|
194
244
|
|
|
195
245
|
|
|
196
246
|
# Wrapper function to maintain backward compatibility
|
|
197
|
-
def find_if_statements(file_content: str) ->
|
|
198
|
-
"""Parse a file to find all if statements
|
|
247
|
+
def find_if_statements(file_content: str) -> IfParseResult:
|
|
248
|
+
"""Parse a file to find all if statements."""
|
|
199
249
|
parser = IfParser(file_content)
|
|
200
250
|
return parser.find_if_statements()
|
troubadix/plugins/__init__.py
CHANGED
|
@@ -39,6 +39,7 @@ from .forking_nasl_functions import CheckForkingNaslFunctions
|
|
|
39
39
|
from .get_kb_on_services import CheckGetKBOnServices
|
|
40
40
|
from .grammar import CheckGrammar
|
|
41
41
|
from .http_links_in_tags import CheckHttpLinksInTags
|
|
42
|
+
from .if_statement_syntax import CheckIfStatementSyntax
|
|
42
43
|
from .illegal_characters import CheckIllegalCharacters
|
|
43
44
|
from .log_messages import CheckLogMessages
|
|
44
45
|
from .malformed_dependencies import CheckMalformedDependencies
|
|
@@ -149,6 +150,7 @@ _FILE_PLUGINS = [
|
|
|
149
150
|
CheckVTPlacement,
|
|
150
151
|
CheckWrongSetGetKBCalls,
|
|
151
152
|
CheckSpacesBeforeDots,
|
|
153
|
+
CheckIfStatementSyntax,
|
|
152
154
|
]
|
|
153
155
|
|
|
154
156
|
# plugins checking all files
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
# SPDX-FileCopyrightText: 2025 Greenbone AG
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Iterator
|
|
7
|
+
|
|
8
|
+
from troubadix.helper.if_block_parser import find_if_statements
|
|
9
|
+
from troubadix.helper.remove_comments import remove_comments
|
|
10
|
+
from troubadix.plugin import FileContentPlugin, LinterError, LinterResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CheckIfStatementSyntax(FileContentPlugin):
|
|
14
|
+
"""Check for syntax errors in if statements.
|
|
15
|
+
|
|
16
|
+
This plugin uses the if statement parser to detect common syntax errors such as:
|
|
17
|
+
- Unclosed parentheses in if conditions
|
|
18
|
+
- Unclosed braces in if blocks
|
|
19
|
+
- Missing statements after if conditions
|
|
20
|
+
- Semicolons after if conditions (which make following blocks always execute)
|
|
21
|
+
- Missing semicolons in single-line expressions
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name = "check_if_statement_syntax"
|
|
25
|
+
|
|
26
|
+
def check_content(
|
|
27
|
+
self,
|
|
28
|
+
nasl_file: Path,
|
|
29
|
+
file_content: str,
|
|
30
|
+
) -> Iterator[LinterResult]:
|
|
31
|
+
"""Check the file content for if statement syntax errors."""
|
|
32
|
+
# Remove comments to avoid false positives from commented code
|
|
33
|
+
comment_free_content = remove_comments(file_content)
|
|
34
|
+
|
|
35
|
+
result = find_if_statements(comment_free_content)
|
|
36
|
+
for error in result.errors:
|
|
37
|
+
yield LinterError(
|
|
38
|
+
error.error_type.value.format(line=error.line),
|
|
39
|
+
file=nasl_file,
|
|
40
|
+
plugin=self.name,
|
|
41
|
+
)
|
|
@@ -38,22 +38,15 @@ class CheckUsingDisplay(FileContentPlugin):
|
|
|
38
38
|
return
|
|
39
39
|
|
|
40
40
|
comment_free_content = remove_comments(file_content)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
str(e),
|
|
46
|
-
file=nasl_file,
|
|
47
|
-
plugin=self.name,
|
|
48
|
-
)
|
|
41
|
+
|
|
42
|
+
display_matches = list(DISPLAY_PATTERN.finditer(comment_free_content))
|
|
43
|
+
|
|
44
|
+
if not display_matches:
|
|
49
45
|
return
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
all_display_matches = list(
|
|
53
|
-
DISPLAY_PATTERN.finditer(comment_free_content)
|
|
54
|
-
)
|
|
47
|
+
if_statements = find_if_statements(comment_free_content).statements
|
|
55
48
|
|
|
56
|
-
for display_match in
|
|
49
|
+
for display_match in display_matches:
|
|
57
50
|
display_pos = display_match.start()
|
|
58
51
|
|
|
59
52
|
# Skip if this match is inside a string literal
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
troubadix/__init__.py,sha256=K7sIXXDrC7YRb7BvIpdQ6ZfG_QkT0qUH_wAlHROVRfM,716
|
|
2
|
-
troubadix/__version__.py,sha256=
|
|
2
|
+
troubadix/__version__.py,sha256=u9FnDhh-YcJ5SxwN459lv4y5J4xuIuXl2bH00srEsSE,103
|
|
3
3
|
troubadix/argparser.py,sha256=-H07Jhqh68_M4Mbjq9qJjTr3MShy_N2pxl2qHA6cfRU,7481
|
|
4
4
|
troubadix/codespell/codespell.additions,sha256=E62zoHJavUhcbMoyBqO2rOUe3OqIXikh7uAiGmNs5uw,524
|
|
5
5
|
troubadix/codespell/codespell.exclude,sha256=8blBv8KRrKU4KQhneHOPth1tSCYdrEoOCI5kYRfhOEA,147451
|
|
@@ -7,13 +7,13 @@ troubadix/codespell/codespell.ignore,sha256=2CP8u6O2VENcDpt2FfEDNmfa1Eh3D80yeYHT
|
|
|
7
7
|
troubadix/helper/__init__.py,sha256=tp2fPLzwGEA_2eiJbvuePiY6rjYSFxx7VUsCV4fSwvw,1110
|
|
8
8
|
troubadix/helper/date_format.py,sha256=2m8EWZPmTQ1kxgv4i5hrPoPlAA8usjz28aRff352zNU,2488
|
|
9
9
|
troubadix/helper/helper.py,sha256=GXapYLii2rLKwkX2ok31YoAdUSizBnyPjWz-aPP6HM8,3105
|
|
10
|
-
troubadix/helper/if_block_parser.py,sha256=
|
|
10
|
+
troubadix/helper/if_block_parser.py,sha256=_51UlsYrELt4GwWzJIU7qc7HIZPdpmExLG8WM8xtjTg,8294
|
|
11
11
|
troubadix/helper/linguistic_exception_handler.py,sha256=Bq7ULjDdWTKUpFNTUX6XMPdD4s4v8eIjZPyqBe8VLws,6811
|
|
12
12
|
troubadix/helper/patterns.py,sha256=qhiAtNQs89Hk6-yRGrjjRO4DdVIMhYLd8IEQ48REpu4,9752
|
|
13
13
|
troubadix/helper/remove_comments.py,sha256=zjhXPQXgKaEOparAdu4aBXcJlEul8LbNaP3uLCYGwHw,1244
|
|
14
14
|
troubadix/helper/text_utils.py,sha256=gBX0box9VtgA0CMfE0u2Vvi1IuOopxUfCQFYn_1wVjE,2543
|
|
15
15
|
troubadix/plugin.py,sha256=3fQPj3Qe_hgwHerlYE4hbdzYMzRU557NxJ-UwtE9mOI,3525
|
|
16
|
-
troubadix/plugins/__init__.py,sha256=
|
|
16
|
+
troubadix/plugins/__init__.py,sha256=7NqTzddmptQZJ_s1yxpamVo0Y8zohAWlEQU2lf103_Y,8745
|
|
17
17
|
troubadix/plugins/badwords.py,sha256=k1A1d2pdXzie87FGGXrykP2BgdZbY5QtmQItupHtNyw,4701
|
|
18
18
|
troubadix/plugins/copyright_text.py,sha256=jYsLWmTbT_A78XQQxQFK-5kMMHkh3xdvlh7mEF2dZGU,3583
|
|
19
19
|
troubadix/plugins/copyright_year.py,sha256=XzM9MHVzOXwNLwHpfuaWj8PUOmswr56SBVOLBdvxjd4,5478
|
|
@@ -32,6 +32,7 @@ troubadix/plugins/forking_nasl_functions.py,sha256=IUtCrTK_sGDx79jAPS8lF_aN8zSG2
|
|
|
32
32
|
troubadix/plugins/get_kb_on_services.py,sha256=oFmcjiF7ZD3x5tEbJEoZNn80y1qUzNgeSZNsogSqaZ0,3401
|
|
33
33
|
troubadix/plugins/grammar.py,sha256=9VPdf4nO-UOE2P8fiAKgh_8GwjGbLztPEixb2AQyeFU,9642
|
|
34
34
|
troubadix/plugins/http_links_in_tags.py,sha256=yKT5SgLo__TJKAfudfIHkoMF0g9VtOP4VI77dANj014,7221
|
|
35
|
+
troubadix/plugins/if_statement_syntax.py,sha256=5BRJwCCghvZn1AfvYzmk8l9S7aRqbVaLHhSKod_Q9zw,1429
|
|
35
36
|
troubadix/plugins/illegal_characters.py,sha256=B6q_RU85AxCjLry56Oc-RhMSpnJU8mTrxclRzi1FVFU,4406
|
|
36
37
|
troubadix/plugins/log_messages.py,sha256=COrnp3bXMG8PRIAD2x5Ka9hk-jI16We9ifXj6JBZI0c,2960
|
|
37
38
|
troubadix/plugins/malformed_dependencies.py,sha256=96W5RJX8CbMkprA2M601qVLgBV-ibJ96bAmtyoSrgFw,3000
|
|
@@ -70,7 +71,7 @@ troubadix/plugins/spelling.py,sha256=3AW5sNtLL67OthKLaCH_y2HCVJ5YH_eyF9xjTJMIDG4
|
|
|
70
71
|
troubadix/plugins/tabs.py,sha256=7zXaTZe4cZoZvrLyqntVfTeNN_W3D8dfQl67QevXxtc,1319
|
|
71
72
|
troubadix/plugins/todo_tbd.py,sha256=MN5fFwBhPmt3JDQ2Hx20B8yUy1vz7LIZC3rDIOzfW9M,1758
|
|
72
73
|
troubadix/plugins/trailing_spaces_tabs.py,sha256=nMly8ZsmGprxHvwCDclKBDRB0eq6JEkjERYKvtStkY4,1873
|
|
73
|
-
troubadix/plugins/using_display.py,sha256=
|
|
74
|
+
troubadix/plugins/using_display.py,sha256=jCl55MXujaO4VlwN_yex269CKnH-oHiMeq8WqvDmGRI,3760
|
|
74
75
|
troubadix/plugins/valid_oid.py,sha256=zoSDyHdePitXJwCtBk4WExtCN36Ql5NATgoQh90oKpI,17824
|
|
75
76
|
troubadix/plugins/valid_script_tag_names.py,sha256=6uMJsBdV-Zx-k1F2_MWmQPHXNo1u0ifuosbftbg-27E,3447
|
|
76
77
|
troubadix/plugins/variable_assigned_in_if.py,sha256=NNz8iuzyQ4rSM6My4WYC1s5TABQqgs7us15PkDA-VV0,3285
|
|
@@ -107,8 +108,8 @@ troubadix/standalone_plugins/no_solution.py,sha256=p_-az9Igl4GH6HnhLLYbYlWIiEP64
|
|
|
107
108
|
troubadix/standalone_plugins/util.py,sha256=JTXGmi-_BJouTNe6QzEosLlXUt9jKW-3fz4db05RJJw,696
|
|
108
109
|
troubadix/standalone_plugins/version_updated.py,sha256=6YHF0OjL5NWszQdsSh7XzlSji1e6Uaqwu_Y6m3R0mvI,4203
|
|
109
110
|
troubadix/troubadix.py,sha256=5__Jz3bYSrya4aG6RCBWxqnsDepXfwXZ3v0bjCzEFi0,6039
|
|
110
|
-
troubadix-25.7.
|
|
111
|
-
troubadix-25.7.
|
|
112
|
-
troubadix-25.7.
|
|
113
|
-
troubadix-25.7.
|
|
114
|
-
troubadix-25.7.
|
|
111
|
+
troubadix-25.7.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
112
|
+
troubadix-25.7.3.dist-info/METADATA,sha256=Gom5h4veT9nQrAy-44KjDlz5TunMgOFJpu13dLmzkAo,4462
|
|
113
|
+
troubadix-25.7.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
114
|
+
troubadix-25.7.3.dist-info/entry_points.txt,sha256=ETEPBi4fKv3o7hzkzceX4838G6g5_5wRdEddYot8N6A,920
|
|
115
|
+
troubadix-25.7.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|