@@ -23,10 +23,128 @@ def __init__(self, cwd="", allow_includes=True):
2323 self ._init_directives ()
2424 self ._path_stack = None
2525
26- def parse_file (self , path , root = None ):
27- LOG .debug ("Parse file: {0}" .format (path ))
28- content = open (path ).read ()
29- return self .parse (content = content , root = root , path_info = path )
26+ def parse_file (self , path , root = None , display_path = None ):
27+ """Parse an nginx configuration file from disk.
28+
29+ Args:
30+ path (str): Filesystem path to the nginx config to parse.
31+ root (Optional[block.Root]): Existing AST root to append into. If None, a new root is created.
32+ display_path (Optional[str]): Path to attribute to parsed nodes (used for stdin/tempfile attribution).
33+
34+ Returns:
35+ block.Root: Parsed configuration tree.
36+
37+ Raises:
38+ InvalidConfiguration: When parsing fails.
39+ """
40+ LOG .debug ("Parse file: {0}" .format (display_path if display_path else path ))
41+ root = self ._ensure_root (root )
42+ try :
43+ parsed = self .parser .parse_path (path )
44+ except ParseException as e :
45+ error_msg = "char {char} (line:{line}, col:{col})" .format (
46+ char = e .loc , line = e .lineno , col = e .col
47+ )
48+ LOG .error (
49+ 'Failed to parse config "{file}": {error}' .format (
50+ file = path , error = error_msg
51+ )
52+ )
53+ raise InvalidConfiguration (error_msg )
54+
55+ current_path = display_path if display_path else path
56+ return self ._build_tree_from_parsed (parsed , root , current_path )
57+
58+ def parse_string (self , content , root = None , path_info = None ):
59+ """Parse nginx configuration provided as a string/bytes.
60+
61+ The content is written to a temporary file so that the underlying
62+ crossplane parser consistently receives a filesystem path (ensuring
63+ identical behavior to file-based parsing).
64+
65+ Args:
66+ content (Union[str, bytes]): Nginx configuration text to parse.
67+ root (Optional[block.Root]): Existing AST root to append into. If None, a new root is created.
68+ path_info (Optional[str]): Path to attribute to parsed nodes (e.g., "<stdin>").
69+
70+ Returns:
71+ block.Root: Parsed configuration tree.
72+
73+ Raises:
74+ InvalidConfiguration: When parsing fails.
75+ """
76+ root = self ._ensure_root (root )
77+ import tempfile
78+ import os
79+ data = content if isinstance (content , (bytes , bytearray )) else content .encode ('utf-8' )
80+ tmp_filename = None
81+ try :
82+ with tempfile .NamedTemporaryFile (mode = 'wb' , suffix = '.conf' , delete = False ) as tmp :
83+ tmp .write (data )
84+ tmp_filename = tmp .name
85+ return self .parse_file (tmp_filename , root = root , display_path = path_info )
86+ except ParseException as e :
87+ error_msg = "char {char} (line:{line}, col:{col})" .format (
88+ char = e .loc , line = e .lineno , col = e .col
89+ )
90+ if path_info :
91+ LOG .error (
92+ 'Failed to parse config "{file}": {error}' .format (
93+ file = path_info , error = error_msg
94+ )
95+ )
96+ else :
97+ LOG .error ("Failed to parse config: {error}" .format (error = error_msg ))
98+ raise InvalidConfiguration (error_msg )
99+ finally :
100+ if tmp_filename :
101+ try :
102+ os .unlink (tmp_filename )
103+ except Exception :
104+ pass
105+
106+ # Backward-compatible alias (deprecated). Prefer parse_string.
107+ def parse (self , content , root = None , path_info = None ):
108+ return self .parse_string (content , root = root , path_info = path_info )
109+
110+ def _ensure_root (self , root ):
111+ """Return provided root or create a new one.
112+
113+ Args:
114+ root (Optional[block.Root]): Existing root node or None.
115+
116+ Returns:
117+ block.Root: Root node.
118+ """
119+ return root if root else block .Root ()
120+
121+ def _build_tree_from_parsed (self , parsed_block , root , current_path ):
122+ """Finalize parsed data into the directive tree.
123+
124+ Handles nginx -T dumps, manages current file attribution, and
125+ appends parsed directives into the provided root.
126+
127+ Args:
128+ parsed_block (list): Parsed representation from RawParser.
129+ root (block.Root): Root node to append into.
130+ current_path (str): Current file path used for attribution.
131+
132+ Returns:
133+ block.Root: The root containing parsed directives.
134+ """
135+ # Handle nginx -T dump format if detected (multi-file with file delimiters)
136+ if len (parsed_block ) and parsed_block [0 ].getName () == "file_delimiter" :
137+ LOG .info ("Switched to parse nginx configuration dump." )
138+ root_filename = self ._prepare_dump (parsed_block )
139+ self .is_dump = True
140+ self .cwd = os .path .dirname (root_filename )
141+ parsed_block = self .configs [root_filename ]
142+
143+ # Parse into the provided root/parent context and keep attribution
144+ self ._path_stack = current_path
145+ self .parse_block (parsed_block , root )
146+ self ._path_stack = current_path
147+ return root
30148
31149 def parse (self , content , root = None , path_info = None ):
32150 if path_info is not None :
0 commit comments