44import os
55import re
66import sys
7+ from collections .abc import Callable
78from functools import cmp_to_key
9+ from typing import Any
810
911
1012class 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,39 @@ 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 : re .Match [str ] | None = re .match (
58+ r"^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\s*" , text_to_check , re .IGNORECASE
59+ )
5660 if match :
5761 return self .month_map .get (match .group (1 ).upper (), 0 ) # 0 for unknown
5862 return 0 # Treat lines without a month at the beginning as "unknown"
5963
60- def human_numeric_sort_key (self , item ) :
64+ def human_numeric_sort_key (self , item : str ) -> float | str :
6165 """Converts a human-readable number string to a comparable tuple (or from the first field)."""
62- text_to_check = item
66+ text_to_check : str = item
6367 if self .args .field_separator :
64- fields = item .split (self .args .field_separator )
68+ fields : list [ str ] = item .split (self .args .field_separator )
6569 if fields :
6670 text_to_check = fields [0 ]
6771 else :
6872 return item # Fallback
6973
70- parts = re .findall (r"(\d+\.?\d*)([KMGTPEZY]?)" , text_to_check , re .IGNORECASE )
74+ parts : list [ tuple [ str , str ]] = re .findall (r"(\d+\.?\d*)([KMGTPEZY]?)" , text_to_check , re .IGNORECASE )
7175 if parts :
72- value , unit = parts [0 ]
73- value = float (value )
76+ value_str : str
77+ unit : str
78+ value_str , unit = parts [0 ]
79+ value : float = float (value_str )
7480 unit = unit .upper ()
7581 if unit == "K" :
7682 value *= 1024
@@ -94,11 +100,11 @@ def human_numeric_sort_key(self, item):
94100 except ValueError :
95101 return text_to_check # Keep original string if not convertible to number
96102
97- def numeric_sort_key (self , item ) :
103+ def numeric_sort_key (self , item : str ) -> float | str :
98104 """Tries to convert the item (or first field) to a float for numeric sorting."""
99- text_to_check = item
105+ text_to_check : str = item
100106 if self .args .field_separator :
101- fields = item .split (self .args .field_separator )
107+ fields : list [ str ] = item .split (self .args .field_separator )
102108 if fields :
103109 text_to_check = fields [0 ]
104110 else :
@@ -109,90 +115,87 @@ def numeric_sort_key(self, item):
109115 except ValueError :
110116 return text_to_check # Keep original string if not a valid number
111117
112- def compare_items (self , item1 , item2 ) :
118+ def compare_items (self , item1 : str , item2 : str ) -> int :
113119 """Compares two items based on the active sorting options."""
114120 if self .args .month_sort :
115- key1 = self .month_sort_key (item1 )
116- key2 = self .month_sort_key (item2 )
121+ key1 : int = self .month_sort_key (item1 )
122+ key2 : int = self .month_sort_key (item2 )
117123 if key1 != key2 :
118124 return key1 - key2
119125
120126 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
127+ key1_human : float | str = self .human_numeric_sort_key (item1 )
128+ key2_human : float | str = self .human_numeric_sort_key (item2 )
129+ if isinstance (key1_human , int | float ) and isinstance (key2_human , int | float ):
130+ if key1_human != key2_human :
131+ return int ( key1_human - key2_human )
126132
127133 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
134+ key1_numeric : float | str = self .numeric_sort_key (item1 )
135+ key2_numeric : float | str = self .numeric_sort_key (item2 )
136+ if isinstance (key1_numeric , int | float ) and isinstance (key2_numeric , int | float ):
137+ if key1_numeric != key2_numeric :
138+ return int ( key1_numeric - key2_numeric )
133139
134140 # 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 :
141+ key1_str : str = self .get_sort_key (item1 )
142+ key2_str : str = self .get_sort_key (item2 )
143+ if key1_str < key2_str :
138144 return - 1
139- elif key1 > key2 :
145+ elif key1_str > key2_str :
140146 return 1
141147 else :
142148 return 0
143149
144- def add_item (self , item ) :
150+ def add_item (self , item : str ) -> None :
145151 """Adds an item to the sorted data using a custom comparison."""
146152 if not self .args .month_sort and not self .args .human_numeric_sort and not self .args .numeric_sort :
147-
148- def key_func (x ):
149- return self .get_sort_key (x )
150-
153+ key_func : Callable [[str ], Any ] = self .get_sort_key
151154 else :
152155 key_func = cmp_to_key (self .compare_items )
153156
154157 bisect .insort_left (self .data , item , key = key_func )
155158
156- def display_sorted (self ):
159+ def display_sorted (self ) -> None :
157160 """Displays the current sorted data."""
158161 output_stream = sys .stderr if self .args .stderr else sys .stdout
159162
160163 if not self .args .stderr :
161164 os .system ("cls" if os .name == "nt" else "clear" )
162165
163166 print ("--- Interactive Sorted Data ---" , file = output_stream )
164- items_to_display = self .data
167+ items_to_display : list [ str ] = self .data
165168 if self .args .unique :
166169 items_to_display = sorted (
167170 set (items_to_display ), key = lambda x : self .data .index (x )
168171 ) # Maintain original order of first occurrence
169172 if self .args .reverse :
170- items_to_display = reversed (items_to_display )
173+ items_to_display = list ( reversed (items_to_display ) )
171174
172175 for item in items_to_display :
173176 print (item , file = output_stream )
174177 print ("-----------------------------" , file = output_stream )
175178
176- def output_final_sorted (self ):
179+ def output_final_sorted (self ) -> None :
177180 """Outputs the final sorted data to stdout (used when stderr flag is set)."""
178- items_to_display = self .data
181+ items_to_display : list [ str ] = self .data
179182 if self .args .unique :
180183 items_to_display = sorted (
181184 set (items_to_display ), key = lambda x : self .data .index (x )
182185 ) # Maintain original order of first occurrence
183186 if self .args .reverse :
184- items_to_display = reversed (items_to_display )
187+ items_to_display = list ( reversed (items_to_display ) )
185188
186189 for item in items_to_display :
187190 print (item , file = sys .stdout )
188191
189- def run (self ):
192+ def run (self ) -> None :
190193 """Runs the interactive sort application."""
191194 output_stream = sys .stderr if self .args .stderr else sys .stdout
192195 print ("Enter lines of text. Press Ctrl+D (or Ctrl+Z then Enter on Windows) to finish." , file = output_stream )
193196 try :
194197 while True :
195- line = input ()
198+ line : str = input ()
196199 self .add_item (line )
197200 self .display_sorted ()
198201 except EOFError :
@@ -205,9 +208,9 @@ def run(self):
205208 print ("\n Exiting." , file = output_stream )
206209
207210
208- def main ():
211+ def main () -> None :
209212 """Main entry point for the interactive-sort command."""
210- parser = argparse .ArgumentParser (description = "Interactively sort input lines." )
213+ parser : argparse . ArgumentParser = argparse .ArgumentParser (description = "Interactively sort input lines." )
211214 parser .add_argument ("-f" , "--ignore-case" , action = "store_true" , help = "fold lower case to upper case characters" )
212215 parser .add_argument ("-i" , "--ignore-nonprinting" , action = "store_true" , help = "consider only printable characters" )
213216 parser .add_argument ("-M" , "--month-sort" , action = "store_true" , help = "compare (unknown) < 'JAN' < ... < 'DEC'" )
@@ -224,10 +227,11 @@ def main():
224227 default = None ,
225228 help = "use FIELD as the field separator when finding an ordering key" ,
226229 )
230+ parser .add_argument ("--stderr" , action = "store_true" , help = "output to stderr for debugging" )
227231
228- args = parser .parse_args ()
232+ args : argparse . Namespace = parser .parse_args ()
229233
230- sorter = InteractiveSort (args )
234+ sorter : InteractiveSort = InteractiveSort (args )
231235 sorter .run ()
232236
233237
0 commit comments