@@ -35,21 +35,74 @@ def read(input: Union[Path, str], encoding: str = 'utf-8') -> Any:
3535 else : return json5 .loads (input_str )
3636
3737def write (file_path : Union [Path , str ], data : Any , encoding : str = 'utf-8' , ensure_ascii : bool = False ,
38- style : str = 'pretty' , atomic : bool = True ) :
38+ style : str = 'pretty' , atomic : bool = True , max_line_length : int = 120 ) -> None :
3939 from . import file
40+ from typing import Optional , List
41+
4042 Path (file_path ).parent .mkdir (parents = True , exist_ok = True )
43+
44+ def format_compact (obj : Any , indent : int = 0 , padded_key : Optional [str ] = None ) -> List [str ]:
45+ indent_spaces = ' ' * indent
46+ line_prefix = padded_key if padded_key else indent_spaces
47+
48+ if isinstance (obj , dict ):
49+
50+ # Try fit whole dict in 1 line
51+ kv_pairs = [f'"{ key } ": { json .dumps (val , separators = ("," ,":" ), ensure_ascii = ensure_ascii )} '
52+ for key ,val in obj .items ()]
53+ single_line_dict = f'{ line_prefix } {{ { ", " .join (kv_pairs )} }}'
54+ if len (single_line_dict ) <= max_line_length :
55+ return [single_line_dict ]
56+
57+ # Else split long line up
58+ lines = [line_prefix + '{' ]
59+ for idx , (key ,val ) in enumerate (obj .items ()):
60+ inner_lines = format_compact (val , indent + 1 , f' { indent_spaces } "{ key } ": ' )
61+ for line in inner_lines : lines .append (line )
62+ if not idx == len (obj ) - 1 : lines [- 1 ] += ',' # append comma except last line
63+ lines .append (indent_spaces + '}' )
64+ return lines
65+
66+ elif isinstance (obj , list ):
67+
68+ # Try fit whole list in 1 line
69+ single_line_list = line_prefix + json .dumps (obj , separators = (',' , ':' ), ensure_ascii = ensure_ascii )
70+ if len (single_line_list ) <= max_line_length :
71+ return [single_line_list ]
72+
73+ # Else split long list up
74+ lines = [line_prefix + '[' ]
75+ if all (not isinstance (item , (dict , list )) for item in obj ): # all items primitives, pack into lines
76+ list_items = [json .dumps (item , ensure_ascii = ensure_ascii ) for item in obj ]
77+ inner_indent = ' ' * (indent + 1 )
78+ current_line_items = []
79+ for item in list_items :
80+ candidate_line = ', ' .join (current_line_items + [item ]) if current_line_items else item
81+ if len (inner_indent + candidate_line ) + 1 <= max_line_length : current_line_items .append (item )
82+ else : # current line full, flush/start new line
83+ if current_line_items : lines .append (inner_indent + ', ' .join (current_line_items ) + ',' )
84+ current_line_items = [item ]
85+ if current_line_items : # flush last line
86+ lines .append (inner_indent + ', ' .join (current_line_items ))
87+ else : # mixed/complex items, format each recursively
88+ for idx , item in enumerate (obj ):
89+ inner_lines = format_compact (item , indent + 1 )
90+ for line in inner_lines : lines .append (line )
91+ if not idx == len (obj ) - 1 : lines [- 1 ] += ','
92+ lines .append (indent_spaces + ']' )
93+ return lines
94+
95+ else : # primitive
96+ return [line_prefix + json .dumps (obj , ensure_ascii = ensure_ascii )]
97+
98+ # Format JSON
4199 if style == 'pretty' : # single key/val spans multi-lines
42100 json_str = json .dumps (data , indent = 2 , ensure_ascii = ensure_ascii )
43- elif style == 'compact' : # single key/val per line
44- lines = ['{' ]
45- items = list (data .items ())
46- for idx , (key , val ) in enumerate (items ):
47- line_end = ',' if idx < len (items ) - 1 else ''
48- inner = f'{{ { json .dumps (val , ensure_ascii = ensure_ascii )[1 :- 1 ]} }}'
49- lines .append (f' "{ key } ": { inner } { line_end } ' )
50- lines .append ('}' )
51- json_str = '\n ' .join (lines )
101+ elif style == 'compact' : # single key/val per line but honors max_line_length
102+ json_str = '\n ' .join (format_compact (data ))
52103 else : # minified to single line
53104 json_str = json .dumps (data , separators = (',' , ':' ), ensure_ascii = ensure_ascii )
54105 json_str += '\n '
106+
107+ # Write to file
55108 getattr (file , 'atomic_write' if atomic else 'write' )(file_path , json_str , encoding = encoding )
0 commit comments