Skip to content

Commit b1809ac

Browse files
CopilotMightyHelper
andcommitted
Add comprehensive type hints to all Python files and integrate mypy with strict mode
Co-authored-by: MightyHelper <32612290+MightyHelper@users.noreply.github.com>
1 parent 35fe6e5 commit b1809ac

6 files changed

Lines changed: 120 additions & 86 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Test with tox
3030
run: tox
3131

32-
lint-and-format:
32+
lint-format-and-type-check:
3333
runs-on: ubuntu-latest
3434

3535
steps:
@@ -50,3 +50,6 @@ jobs:
5050

5151
- name: Check formatting with ruff
5252
run: tox -e format-check
53+
54+
- name: Type check with mypy
55+
run: tox -e mypy

pyproject.toml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,21 @@ ignore = [
6868
quote-style = "double"
6969
indent-style = "space"
7070
skip-magic-trailing-comma = false
71-
line-ending = "auto"
71+
line-ending = "auto"
72+
73+
# mypy configuration
74+
[tool.mypy]
75+
python_version = "3.8"
76+
strict = true
77+
warn_return_any = true
78+
warn_unused_configs = true
79+
disallow_untyped_defs = true
80+
disallow_incomplete_defs = true
81+
check_untyped_defs = true
82+
disallow_untyped_decorators = true
83+
no_implicit_optional = true
84+
warn_redundant_casts = true
85+
warn_unused_ignores = true
86+
warn_no_return = true
87+
warn_unreachable = true
88+
strict_equality = true

src/interactive_sort/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Interactive Sort - A tool for interactively sorting lines of text."""
22

3-
__version__ = "1.0.0"
4-
__author__ = "MightyHelper"
5-
__description__ = "Interactive sorting utility for text lines with various sorting options"
3+
from typing import List
4+
5+
__version__: str = "1.0.0"
6+
__author__: str = "MightyHelper"
7+
__description__: str = "Interactive sorting utility for text lines with various sorting options"
68

79
from .main import InteractiveSort, main
810

9-
__all__ = ["InteractiveSort", "main"]
11+
__all__: List[str] = ["InteractiveSort", "main"]

src/interactive_sort/main.py

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import re
66
import sys
77
from functools import cmp_to_key
8+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
9+
import re
810

911

1012
class InteractiveSort:
11-
def __init__(self, args):
12-
self.data = []
13-
self.args = args
14-
self.month_map = {
13+
def __init__(self, args: argparse.Namespace) -> None:
14+
self.data: List[str] = []
15+
self.args: argparse.Namespace = args
16+
self.month_map: Dict[str, int] = {
1517
"JAN": 1,
1618
"FEB": 2,
1719
"MAR": 3,
@@ -26,11 +28,11 @@ def __init__(self, args):
2628
"DEC": 12,
2729
}
2830

29-
def get_sort_key(self, item):
31+
def get_sort_key(self, item: str) -> str:
3032
"""Generates a key for sorting based on the provided arguments."""
31-
key = item
33+
key: str = item
3234
if self.args.field_separator:
33-
fields = key.split(self.args.field_separator)
35+
fields: List[str] = key.split(self.args.field_separator)
3436
if fields:
3537
key = fields[0]
3638
else:
@@ -42,35 +44,37 @@ def get_sort_key(self, item):
4244
key = "".join(filter(str.isprintable, key))
4345
return key
4446

45-
def month_sort_key(self, item):
47+
def month_sort_key(self, item: str) -> int:
4648
"""Extracts and converts the month from the beginning of the string (or first field)."""
47-
text_to_check = item
49+
text_to_check: str = item
4850
if self.args.field_separator:
49-
fields = item.split(self.args.field_separator)
51+
fields: List[str] = item.split(self.args.field_separator)
5052
if fields:
5153
text_to_check = fields[0]
5254
else:
5355
return 0
5456

55-
match = re.match(r"^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\s*", text_to_check, re.IGNORECASE)
57+
match: Optional[re.Match[str]] = re.match(r"^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\s*", text_to_check, re.IGNORECASE)
5658
if match:
5759
return self.month_map.get(match.group(1).upper(), 0) # 0 for unknown
5860
return 0 # Treat lines without a month at the beginning as "unknown"
5961

60-
def human_numeric_sort_key(self, item):
62+
def human_numeric_sort_key(self, item: str) -> Union[float, str]:
6163
"""Converts a human-readable number string to a comparable tuple (or from the first field)."""
62-
text_to_check = item
64+
text_to_check: str = item
6365
if self.args.field_separator:
64-
fields = item.split(self.args.field_separator)
66+
fields: List[str] = item.split(self.args.field_separator)
6567
if fields:
6668
text_to_check = fields[0]
6769
else:
6870
return item # Fallback
6971

70-
parts = re.findall(r"(\d+\.?\d*)([KMGTPEZY]?)", text_to_check, re.IGNORECASE)
72+
parts: List[Tuple[str, str]] = re.findall(r"(\d+\.?\d*)([KMGTPEZY]?)", text_to_check, re.IGNORECASE)
7173
if parts:
72-
value, unit = parts[0]
73-
value = float(value)
74+
value_str: str
75+
unit: str
76+
value_str, unit = parts[0]
77+
value: float = float(value_str)
7478
unit = unit.upper()
7579
if unit == "K":
7680
value *= 1024
@@ -94,11 +98,11 @@ def human_numeric_sort_key(self, item):
9498
except ValueError:
9599
return text_to_check # Keep original string if not convertible to number
96100

97-
def numeric_sort_key(self, item):
101+
def numeric_sort_key(self, item: str) -> Union[float, str]:
98102
"""Tries to convert the item (or first field) to a float for numeric sorting."""
99-
text_to_check = item
103+
text_to_check: str = item
100104
if self.args.field_separator:
101-
fields = item.split(self.args.field_separator)
105+
fields: List[str] = item.split(self.args.field_separator)
102106
if fields:
103107
text_to_check = fields[0]
104108
else:
@@ -109,90 +113,90 @@ def numeric_sort_key(self, item):
109113
except ValueError:
110114
return text_to_check # Keep original string if not a valid number
111115

112-
def compare_items(self, item1, item2):
116+
def compare_items(self, item1: str, item2: str) -> int:
113117
"""Compares two items based on the active sorting options."""
114118
if self.args.month_sort:
115-
key1 = self.month_sort_key(item1)
116-
key2 = self.month_sort_key(item2)
119+
key1: int = self.month_sort_key(item1)
120+
key2: int = self.month_sort_key(item2)
117121
if key1 != key2:
118122
return key1 - key2
119123

120124
if self.args.human_numeric_sort:
121-
key1 = self.human_numeric_sort_key(item1)
122-
key2 = self.human_numeric_sort_key(item2)
123-
if isinstance(key1, (int, float)) and isinstance(key2, (int, float)):
124-
if key1 != key2:
125-
return key1 - key2
125+
key1_human: Union[float, str] = self.human_numeric_sort_key(item1)
126+
key2_human: Union[float, str] = self.human_numeric_sort_key(item2)
127+
if isinstance(key1_human, (int, float)) and isinstance(key2_human, (int, float)):
128+
if key1_human != key2_human:
129+
return int(key1_human - key2_human)
126130

127131
if self.args.numeric_sort:
128-
key1 = self.numeric_sort_key(item1)
129-
key2 = self.numeric_sort_key(item2)
130-
if isinstance(key1, (int, float)) and isinstance(key2, (int, float)):
131-
if key1 != key2:
132-
return key1 - key2
132+
key1_numeric: Union[float, str] = self.numeric_sort_key(item1)
133+
key2_numeric: Union[float, str] = self.numeric_sort_key(item2)
134+
if isinstance(key1_numeric, (int, float)) and isinstance(key2_numeric, (int, float)):
135+
if key1_numeric != key2_numeric:
136+
return int(key1_numeric - key2_numeric)
133137

134138
# Default string comparison (after applying ignore-case and ignore-nonprinting)
135-
key1 = self.get_sort_key(item1)
136-
key2 = self.get_sort_key(item2)
137-
if key1 < key2:
139+
key1_str: str = self.get_sort_key(item1)
140+
key2_str: str = self.get_sort_key(item2)
141+
if key1_str < key2_str:
138142
return -1
139-
elif key1 > key2:
143+
elif key1_str > key2_str:
140144
return 1
141145
else:
142146
return 0
143147

144-
def add_item(self, item):
148+
def add_item(self, item: str) -> None:
145149
"""Adds an item to the sorted data using a custom comparison."""
146150
if not self.args.month_sort and not self.args.human_numeric_sort and not self.args.numeric_sort:
147151

148-
def key_func(x):
152+
def key_func(x: str) -> str:
149153
return self.get_sort_key(x)
150154

151155
else:
152156
key_func = cmp_to_key(self.compare_items)
153157

154158
bisect.insort_left(self.data, item, key=key_func)
155159

156-
def display_sorted(self):
160+
def display_sorted(self) -> None:
157161
"""Displays the current sorted data."""
158162
output_stream = sys.stderr if self.args.stderr else sys.stdout
159163

160164
if not self.args.stderr:
161165
os.system("cls" if os.name == "nt" else "clear")
162166

163167
print("--- Interactive Sorted Data ---", file=output_stream)
164-
items_to_display = self.data
168+
items_to_display: List[str] = self.data
165169
if self.args.unique:
166170
items_to_display = sorted(
167171
set(items_to_display), key=lambda x: self.data.index(x)
168172
) # Maintain original order of first occurrence
169173
if self.args.reverse:
170-
items_to_display = reversed(items_to_display)
174+
items_to_display = list(reversed(items_to_display))
171175

172176
for item in items_to_display:
173177
print(item, file=output_stream)
174178
print("-----------------------------", file=output_stream)
175179

176-
def output_final_sorted(self):
180+
def output_final_sorted(self) -> None:
177181
"""Outputs the final sorted data to stdout (used when stderr flag is set)."""
178-
items_to_display = self.data
182+
items_to_display: List[str] = self.data
179183
if self.args.unique:
180184
items_to_display = sorted(
181185
set(items_to_display), key=lambda x: self.data.index(x)
182186
) # Maintain original order of first occurrence
183187
if self.args.reverse:
184-
items_to_display = reversed(items_to_display)
188+
items_to_display = list(reversed(items_to_display))
185189

186190
for item in items_to_display:
187191
print(item, file=sys.stdout)
188192

189-
def run(self):
193+
def run(self) -> None:
190194
"""Runs the interactive sort application."""
191195
output_stream = sys.stderr if self.args.stderr else sys.stdout
192196
print("Enter lines of text. Press Ctrl+D (or Ctrl+Z then Enter on Windows) to finish.", file=output_stream)
193197
try:
194198
while True:
195-
line = input()
199+
line: str = input()
196200
self.add_item(line)
197201
self.display_sorted()
198202
except EOFError:
@@ -205,9 +209,9 @@ def run(self):
205209
print("\nExiting.", file=output_stream)
206210

207211

208-
def main():
212+
def main() -> None:
209213
"""Main entry point for the interactive-sort command."""
210-
parser = argparse.ArgumentParser(description="Interactively sort input lines.")
214+
parser: argparse.ArgumentParser = argparse.ArgumentParser(description="Interactively sort input lines.")
211215
parser.add_argument("-f", "--ignore-case", action="store_true", help="fold lower case to upper case characters")
212216
parser.add_argument("-i", "--ignore-nonprinting", action="store_true", help="consider only printable characters")
213217
parser.add_argument("-M", "--month-sort", action="store_true", help="compare (unknown) < 'JAN' < ... < 'DEC'")
@@ -224,10 +228,11 @@ def main():
224228
default=None,
225229
help="use FIELD as the field separator when finding an ordering key",
226230
)
231+
parser.add_argument("--stderr", action="store_true", help="output to stderr for debugging")
227232

228-
args = parser.parse_args()
233+
args: argparse.Namespace = parser.parse_args()
229234

230-
sorter = InteractiveSort(args)
235+
sorter: InteractiveSort = InteractiveSort(args)
231236
sorter.run()
232237

233238

0 commit comments

Comments
 (0)