@@ -64,10 +64,11 @@ class Section:
6464class AmendmentTemplateValidator :
6565 """Validates Amendment specs against the template structure."""
6666
67- # Map section titles to their expected subsections
68- # Key is the section title, value is dict of subsection numbers to titles
67+ # Map section title patterns to their expected subsections
68+ # Key is a regex pattern to match section titles,
69+ # value is dict of subsection numbers to titles
6970 SECTION_TEMPLATES = {
70- "Serialized Types " : {
71+ r"SType: " : {
7172 "required_subsections" : {
7273 "1" : "SType Value" ,
7374 "2" : "JSON Representation" ,
@@ -78,7 +79,7 @@ class AmendmentTemplateValidator:
7879 "3" : "Additional Accepted JSON Inputs" ,
7980 }
8081 },
81- "Ledger Entries " : {
82+ r "Ledger Entry: " : {
8283 "required_subsections" : {
8384 "1" : "Object Identifier" ,
8485 "2" : "Fields" ,
@@ -94,7 +95,7 @@ class AmendmentTemplateValidator:
9495 "7" : "Freeze/Lock" ,
9596 }
9697 },
97- "Transactions " : {
98+ r"Transaction: " : {
9899 "required_subsections" : {
99100 "1" : "Fields" ,
100101 "2" : "Transaction Fee" ,
@@ -106,37 +107,46 @@ class AmendmentTemplateValidator:
106107 "5" : "Metadata Fields" ,
107108 }
108109 },
109- "Permissions " : {
110+ r"Permission: " : {
110111 "required_subsections" : {},
111112 "optional_subsections" : {}
112113 },
113- "API/RPCs " : {
114+ r"RPC: " : {
114115 "required_subsections" : {},
115116 "optional_subsections" : {}
116117 },
117118 }
118119
119120 # Placeholder patterns that should not appear in final specs
121+ # These are now in italicized bracket format: _[...]_
120122 PLACEHOLDER_PATTERNS = [
121123 r'\[STypeName\]' ,
122124 r'\[LedgerEntryName\]' ,
123125 r'\[TransactionName\]' ,
124- r'\[XXXX\]' ,
126+ r'\[PermissionName\]' ,
127+ r'\[rpc_method_name\]' ,
128+ r'0x\[XXXX\]' ,
125129 r'\[field_name\]' ,
130+ r'\[FieldName\]' ,
126131 r'\[api_method_name\]' ,
127132 r'\[CustomField\d+\]' ,
128133 r'\[EntryTypeValue\]' ,
129134 r'\[TYPE\]' ,
130- r'\[Yes/No\]' ,
135+ r'\[Yes/No(?:/Conditional)?\]' ,
136+ r'\[Standard/Custom(?:/None)?\]' ,
131137 r'\[Value/N/A\]' ,
132- r'\[Description.*?\]' ,
133- r'\[Provide.*?\]' ,
134- r'\[Specify.*?\]' ,
135- r'\[Describe.*?\]' ,
136- r'\[List.*?\]' ,
137- r'\[If.*?\]' ,
138- r'\[Add more.*?\]' ,
139- r'\[Remove example.*?\]' ,
138+ r'\[r-address\]' ,
139+ r'\[example value\]' ,
140+ r'_\[Description.*?\]' ,
141+ r'_\[Provide.*?\]' ,
142+ r'_\[Specify.*?\]' ,
143+ r'_\[Describe.*?\]' ,
144+ r'_\[List.*?\]' ,
145+ r'_\[If .*?\]' ,
146+ r'_\[Add more.*?\]' ,
147+ r'_\[Remove example.*?\]' ,
148+ r'_\[Any explanatory.*?\]' ,
149+ r'_\[Detailed explanation.*?\]' ,
140150 ]
141151
142152 def __init__ (self , file_path : Path ):
@@ -219,12 +229,15 @@ def _validate_has_required_sections(self):
219229 """Validate that at least one template section exists."""
220230 main_sections = [s for s in self .sections if s .level == 2 ]
221231
222- # Get titles of main sections
223- section_titles = {s .title for s in main_sections }
224-
225232 # Check if at least one template section exists
226- template_sections = set (self .SECTION_TEMPLATES .keys ())
227- has_template_section = bool (section_titles & template_sections )
233+ has_template_section = False
234+ for section in main_sections :
235+ for pattern in self .SECTION_TEMPLATES .keys ():
236+ if re .search (pattern , section .title ):
237+ has_template_section = True
238+ break
239+ if has_template_section :
240+ break
228241
229242 if not has_template_section :
230243 # No template sections found - this might be an old spec
@@ -236,13 +249,15 @@ def _validate_section_structure(self):
236249 # Check each main section that matches a template
237250 for section in self .sections :
238251 if section .level == 2 :
239- # Check if this section matches a template
240- if section .title in self .SECTION_TEMPLATES :
241- self ._validate_subsections (section )
252+ # Check if this section matches a template pattern
253+ for pattern in self .SECTION_TEMPLATES .keys ():
254+ if re .search (pattern , section .title ):
255+ self ._validate_subsections (section , pattern )
256+ break
242257
243- def _validate_subsections (self , parent_section : Section ):
258+ def _validate_subsections (self , parent_section : Section , pattern : str ):
244259 """Validate subsections for a given parent section."""
245- template = self .SECTION_TEMPLATES .get (parent_section . title )
260+ template = self .SECTION_TEMPLATES .get (pattern )
246261 if not template :
247262 return
248263
@@ -253,39 +268,26 @@ def _validate_subsections(self, parent_section: Section):
253268 if s .number .startswith (parent_num + "." ) and s .level == 3
254269 ]
255270
256- # Group subsections by their first-level parent (e.g., 2.1, 2.2)
257- # This handles cases where there are multiple instances
258- # (e.g., multiple ledger entries or transactions)
259- first_level_groups = {}
260- for s in subsections :
261- parts = s .number .split ('.' )
262- if len (parts ) >= 2 :
263- first_level = f"{ parts [0 ]} .{ parts [1 ]} "
264- if first_level not in first_level_groups :
265- first_level_groups [first_level ] = []
266- first_level_groups [first_level ].append (s )
267-
268- # For each first-level group, validate required subsections
271+ # For template sections, subsections are directly under the parent
272+ # (e.g., 1.1, 1.2, 1.3 for SType section 1)
273+ # Check each required subsection
269274 required_subs = template ["required_subsections" ]
270275
271- for first_level in first_level_groups :
272- # Check each required subsection
273- for sub_num , sub_title in required_subs .items ():
274- expected_num = f"{ first_level } .{ sub_num } "
276+ for sub_num , sub_title in required_subs .items ():
277+ expected_num = f"{ parent_num } .{ sub_num } "
275278
276- # Find this subsection in the group
277- found = any (
278- s .number == expected_num
279- for s in self .sections
280- if s .level == 4
281- )
279+ # Find this subsection
280+ found = any (
281+ s .number == expected_num and s .title == sub_title
282+ for s in subsections
283+ )
282284
283- if not found :
284- self .errors .append (ValidationError (
285- str (self .file_path ), parent_section .line_number ,
286- f"Missing required subsection { expected_num } "
287- f"'{ sub_title } ' under { parent_section .title } "
288- ))
285+ if not found :
286+ self .errors .append (ValidationError (
287+ str (self .file_path ), parent_section .line_number ,
288+ f"Missing required subsection { expected_num } "
289+ f"'{ sub_title } ' under { parent_section .title } "
290+ ))
289291
290292 def _validate_no_placeholders (self ):
291293 """Check for template placeholder text that should be replaced."""
0 commit comments