55import re
66import sys
77from functools import cmp_to_key
8+ from typing import Any , Callable , Dict , List , Optional , Tuple , Union
9+ import re
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,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 ("\n Exiting." , 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