2626
2727ParsedData = Union [Dict [str , Any ], List [Any ]]
2828
29- LIST_ACCESS_SYMBOL = '@'
30- SLICE_SYMBOL = ':'
29+ LIST_ACCESS_SYMBOL = "@"
30+ SLICE_SYMBOL = ":"
3131
3232######################################################################
3333# Output formatting functions
@@ -79,6 +79,7 @@ def bold(text: str) -> str:
7979
8080class ParseError (Exception ):
8181 """Custom exception for parsing errors."""
82+
8283 pass
8384
8485
@@ -93,6 +94,7 @@ def parse_ini(file: StringIO) -> Dict[str, Dict[str, str]]:
9394 The parsed content as a dictionary.
9495 """
9596 from configparser import ConfigParser
97+
9698 config = ConfigParser ()
9799 config .read_file (file )
98100 return {s : dict (config .items (s )) for s in config .sections ()}
@@ -109,11 +111,11 @@ def parse_yaml(file: StringIO) -> ParsedData:
109111 The parsed content.
110112 """
111113 import yaml
114+
112115 try :
113116 return yaml .safe_load (file )
114117 except yaml .YAMLError as e :
115- raise ParseError (
116- f"[ERROR] { file .name } : Unable to parse YAML file: { str (e )} " )
118+ raise ParseError (f"[ERROR] { file .name } : Unable to parse YAML file: { str (e )} " )
117119
118120
119121def parse_json (file : StringIO ) -> ParsedData :
@@ -127,11 +129,11 @@ def parse_json(file: StringIO) -> ParsedData:
127129 The parsed content.
128130 """
129131 import json
132+
130133 try :
131134 return json .load (file )
132135 except json .JSONDecodeError as e :
133- raise ParseError (
134- f"[ERROR] { file .name } : Unable to parse JSON file: { str (e )} " )
136+ raise ParseError (f"[ERROR] { file .name } : Unable to parse JSON file: { str (e )} " )
135137
136138
137139def parse_toml (file : StringIO ) -> ParsedData :
@@ -145,18 +147,18 @@ def parse_toml(file: StringIO) -> ParsedData:
145147 The parsed content.
146148 """
147149 import toml
150+
148151 try :
149152 return toml .load (file )
150153 except toml .TomlDecodeError as e :
151- raise ParseError (
152- f"[ERROR] { file .name } : Unable to parse TOML file: { str (e )} " )
154+ raise ParseError (f"[ERROR] { file .name } : Unable to parse TOML file: { str (e )} " )
153155
154156
155157FORMATS = [
156- ([' .json' ], parse_json ),
157- ([' .yaml' , ' .yml' ], parse_yaml ),
158- ([' .toml' ], parse_toml ),
159- ([' .ini' ], parse_ini )
158+ ([" .json" ], parse_json ),
159+ ([" .yaml" , " .yml" ], parse_yaml ),
160+ ([" .toml" ], parse_toml ),
161+ ([" .ini" ], parse_ini ),
160162]
161163
162164
@@ -174,7 +176,7 @@ def parse_file(filename: str) -> ParsedData:
174176 parsers = [parser for fmts , parser in FORMATS if ext in fmts ]
175177
176178 try :
177- with open (filename , 'r' ) as file :
179+ with open (filename , "r" ) as file :
178180 content = file .read ().strip ()
179181 if not content :
180182 raise ValueError (f"[ERROR] { filename } : File is empty" )
@@ -190,6 +192,7 @@ def parse_file(filename: str) -> ParsedData:
190192 except Exception as e :
191193 raise ValueError (f"[ERROR] { filename } : Unable to parse file: { str (e )} " )
192194
195+
193196######################################################################
194197# Formatting
195198######################################################################
@@ -207,34 +210,38 @@ def format_output(data: Any, output_format: str) -> str:
207210 The formatted output.
208211 """
209212
210- if output_format == ' raw' :
213+ if output_format == " raw" :
211214 return str (data )
212- if output_format in (' formatted' , ' json' ):
215+ if output_format in (" formatted" , " json" ):
213216 import json
214217
215218 def date_converter (o ):
216219 if isinstance (o , (date , datetime )):
217220 return o .isoformat ()
218221 return o
219222
220- indent = 4 if output_format == ' formatted' else None
223+ indent = 4 if output_format == " formatted" else None
221224 return json .dumps (data , indent = indent , default = date_converter )
222- elif output_format == ' yaml' :
225+ elif output_format == " yaml" :
223226 import yaml
227+
224228 return yaml .dump (data , default_flow_style = False )
225- elif output_format == ' toml' :
229+ elif output_format == " toml" :
226230 import toml
231+
227232 # Check if it's a list of dicts
228233 if isinstance (data , list ) and all (isinstance (item , dict ) for item in data ):
229234 # If it's a list of dictionaries, wrap it in a dictionary with a key like "items"
230235 return toml .dumps ({"items" : data }) # Wrap the list
231236 else :
232237 return toml .dumps (data ) # Handle other cases as before
233238
234- elif output_format == ' ini' :
239+ elif output_format == " ini" :
235240 config = ConfigParser ()
236- if not isinstance (data , dict ) or not all (isinstance (v , dict ) for v in data .values ()):
237- data = {'default' : data }
241+ if not isinstance (data , dict ) or not all (
242+ isinstance (v , dict ) for v in data .values ()
243+ ):
244+ data = {"default" : data }
238245 for section , values in data .items ():
239246 config [section ] = values
240247 output = StringIO ()
@@ -243,6 +250,7 @@ def date_converter(o):
243250 else :
244251 return str (data )
245252
253+
246254######################################################################
247255# Data access functions
248256######################################################################
@@ -266,6 +274,7 @@ def access_list(data: Any, key: str, index: str) -> Any:
266274 else :
267275 return data .get (key )[int (index )]
268276
277+
269278def from_attr_chain (data : Dict [str , Any ], lookup_chain : str ) -> Any :
270279 """
271280 Accesses a nested dictionary value with an attribute chain encoded by a dot-separated string.
@@ -278,22 +287,22 @@ def from_attr_chain(data: Dict[str, Any], lookup_chain: str) -> Any:
278287 The value at the specified nested key, or None if the key doesn't exist.
279288 """
280289 if data is None :
281- chain = lookup_chain .split ('.' )[0 ]
282- raise KeyError (
283- f"[ERROR] key '{ bold ({chain })} ' not found in { italics ('' )} " )
290+ chain = lookup_chain .split ("." )[0 ]
291+ raise KeyError (f"[ERROR] key '{ bold ({chain })} ' not found in { italics ('' )} " )
284292 found_keys = []
285- for key in lookup_chain .split ('.' ):
293+ for key in lookup_chain .split ("." ):
286294 if LIST_ACCESS_SYMBOL in key :
287295 key , index = key .split (LIST_ACCESS_SYMBOL )
288296 data = access_list (data , key , index )
289297 else :
290298 data = data .get (key )
291299 if data is None :
292- keys = '.' .join (found_keys )
300+ keys = "." .join (found_keys )
293301 raise KeyError (f"[ERROR] key '{ key } ' not found in { keys } " )
294302 found_keys .append (key )
295303 return data
296304
305+
297306######################################################################
298307# Argument parsing, main, and run functions
299308######################################################################
@@ -310,17 +319,37 @@ def parse_args(args: List[str]) -> Tuple[str, str, str, bool]:
310319 The filename, lookup chain, output format, and check_install flag.
311320 """
312321 parser = argparse .ArgumentParser (add_help = False )
313- parser .add_argument ('file' , type = str , nargs = '?' , help = 'The file to read from' )
314- parser .add_argument ('dot_separated_key' , type = str , nargs = '?' , help = 'The dot-separated key to look up' )
315- parser .add_argument ('--output' , type = str , default = 'raw' , help = 'The output format (raw, formatted, json, yaml, toml, ini)' )
316- parser .add_argument ('--check-install' , action = 'store_true' , help = 'Check if required packages are installed' )
322+ parser .add_argument ("file" , type = str , nargs = "?" , help = "The file to read from" )
323+ parser .add_argument (
324+ "dot_separated_key" ,
325+ type = str ,
326+ nargs = "?" ,
327+ help = "The dot-separated key to look up" ,
328+ )
329+ parser .add_argument (
330+ "--output" ,
331+ type = str ,
332+ default = "raw" ,
333+ help = "The output format (raw, formatted, json, yaml, toml, ini)" ,
334+ )
335+ parser .add_argument (
336+ "--check-install" ,
337+ action = "store_true" ,
338+ help = "Check if required packages are installed" ,
339+ )
317340
318341 if args is None or len (args ) < 1 :
319342 print (USAGE )
320343 sys .exit (2 )
321344
322345 parsed_args = parser .parse_args (args )
323- return parsed_args .file , parsed_args .dot_separated_key , parsed_args .output , parsed_args .check_install
346+ return (
347+ parsed_args .file ,
348+ parsed_args .dot_separated_key ,
349+ parsed_args .output ,
350+ parsed_args .check_install ,
351+ )
352+
324353
325354def run (args : List [str ] = None ) -> None :
326355 """
@@ -336,6 +365,11 @@ def run(args: List[str] = None) -> None:
336365 check_install ()
337366 return
338367
368+ # Check if lookup_chain is provided
369+ if lookup_chain is None :
370+ print (USAGE )
371+ sys .exit (2 ) # Invalid usage
372+
339373 # gets the parsed data
340374 try :
341375 data = parse_file (filename )
@@ -361,10 +395,11 @@ def main() -> None:
361395 """
362396 run (sys .argv [1 :])
363397
398+
364399def check_install ():
365- import json , yaml , toml
366400 print ("Dotcat is good to go." )
367401 return
368402
369- if __name__ == '__main__' :
403+
404+ if __name__ == "__main__" :
370405 main ()
0 commit comments