Skip to content

Commit fdbf1c6

Browse files
committed
make script more robust
1 parent 68c8bbd commit fdbf1c6

File tree

2 files changed

+161
-10
lines changed

2 files changed

+161
-10
lines changed

scripts/validate_xls_template.py

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
from dataclasses import dataclass
3131
from pathlib import Path
3232
from typing import List, Optional, Tuple
33+
import urllib.request
34+
import urllib.error
3335

3436
from markdown_it import MarkdownIt
3537
from xls_parser import extract_xls_metadata
@@ -86,6 +88,10 @@ class XLSTemplateValidator:
8688
# Valid category values
8789
VALID_CATEGORIES = ["Amendment", "System", "Ecosystem", "Meta"]
8890

91+
# Note: Ledger entry and transaction existence is now determined dynamically
92+
# by checking if the documentation exists on xrpl.org rather than
93+
# maintaining hardcoded lists.
94+
8995
# Amendment template section patterns and their required subsections
9096
AMENDMENT_SECTION_TEMPLATES = {
9197
r"SType:": {
@@ -228,11 +234,11 @@ def _parse_sections(self):
228234
# Walk through tokens to find headings
229235
for i, token in enumerate(tokens):
230236
if token.type == 'heading_open':
231-
# Get the heading level (h2 = level 2, h3 = level 3, etc.)
237+
# Get the heading level (h1 = level 1, h2 = level 2, etc.)
232238
level = int(token.tag[1])
233239

234-
# Only process h2 (top-level sections)
235-
if level != 2:
240+
# Skip h1 (document title); process h2+ for structure
241+
if level < 2:
236242
continue
237243

238244
# The next token should be 'inline' containing heading text
@@ -396,17 +402,125 @@ def _validate_amendment_structure(self):
396402
for section in main_sections:
397403
for pattern, template in self.AMENDMENT_SECTION_TEMPLATES.items():
398404
if re.search(pattern, section.title):
405+
# Check if this is a Ledger Entry section for an
406+
# existing ledger entry type
407+
is_existing_ledger_entry = False
408+
if pattern == r"Ledger Entry:":
409+
is_existing_ledger_entry = (
410+
self._is_existing_ledger_entry(section.title)
411+
)
412+
413+
# Check if this is a Transaction section for an
414+
# existing transaction type
415+
is_existing_transaction = False
416+
if pattern == r"Transaction:":
417+
is_existing_transaction = (
418+
self._is_existing_transaction(section.title)
419+
)
420+
399421
self._validate_subsections(
400422
section,
401423
template["required_subsections"],
402-
template["optional_subsections"]
424+
template["optional_subsections"],
425+
is_existing_ledger_entry,
426+
is_existing_transaction
403427
)
404428

429+
def _is_existing_ledger_entry(self, section_title: str) -> bool:
430+
"""
431+
Check if a Ledger Entry section is for an existing ledger entry.
432+
433+
Extracts the ledger entry name from the section title and checks
434+
if the ledger entry exists on xrpl.org by fetching the URL:
435+
https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/[lowercase]
436+
437+
Args:
438+
section_title: The title of the section
439+
(e.g., "Ledger Entry: `AccountRoot`")
440+
441+
Returns:
442+
True if this is an existing ledger entry type, False otherwise
443+
"""
444+
# Extract ledger entry name from title like "Ledger Entry: `Foo`"
445+
# or "2. Ledger Entry: `Foo`"
446+
match = re.search(r'Ledger Entry:\s*`?([A-Za-z]+)`?', section_title)
447+
if not match:
448+
return False
449+
450+
entry_name = match.group(1)
451+
452+
# Convert to lowercase for URL
453+
lowercase_name = entry_name.lower()
454+
url = (
455+
"https://xrpl.org/docs/references/protocol/"
456+
f"ledger-data/ledger-entry-types/{lowercase_name}"
457+
)
458+
459+
try:
460+
# Try to fetch the URL
461+
req = urllib.request.Request(url, method='HEAD')
462+
with urllib.request.urlopen(req, timeout=5) as response:
463+
# If we get a 200 response, the ledger entry exists
464+
return response.status == 200
465+
except urllib.error.HTTPError as e:
466+
# 404 means the ledger entry doesn't exist
467+
if e.code == 404:
468+
return False
469+
# Other errors (500, etc.) - assume it exists to be safe
470+
return True
471+
except (urllib.error.URLError, TimeoutError):
472+
# Network error - assume it exists to be safe
473+
return True
474+
475+
def _is_existing_transaction(self, section_title: str) -> bool:
476+
"""
477+
Check if a Transaction section is for an existing transaction type.
478+
479+
Extracts the transaction name from the section title and checks
480+
if the transaction exists on xrpl.org by fetching the URL:
481+
https://xrpl.org/docs/references/protocol/transactions/types/[lowercase]
482+
483+
Args:
484+
section_title: The title of the section (e.g., "Transaction: `Payment`")
485+
486+
Returns:
487+
True if this is an existing transaction type, False otherwise
488+
"""
489+
# Extract transaction name from title like "Transaction: `Payment`"
490+
# or "3. Transaction: `Payment`"
491+
match = re.search(r'Transaction:\s*`?([A-Za-z]+)`?', section_title)
492+
if not match:
493+
return False
494+
495+
transaction_name = match.group(1)
496+
497+
# Convert to lowercase for URL
498+
lowercase_name = transaction_name.lower()
499+
url = f"https://xrpl.org/docs/references/protocol/transactions/types/{lowercase_name}"
500+
501+
try:
502+
# Try to fetch the URL
503+
req = urllib.request.Request(url, method='HEAD')
504+
with urllib.request.urlopen(req, timeout=5) as response:
505+
# If we get a 200 response, the transaction exists
506+
return response.status == 200
507+
except urllib.error.HTTPError as e:
508+
# 404 means the transaction doesn't exist
509+
if e.code == 404:
510+
return False
511+
# Other errors (500, etc.) - assume it exists to be safe
512+
return True
513+
except (urllib.error.URLError, TimeoutError):
514+
# Network error - assume it exists to be safe
515+
return True
516+
405517
def _validate_subsections(
406518
self,
407519
parent_section: Section,
408520
required_subsections: dict,
409-
optional_subsections: dict
521+
optional_subsections: dict,
522+
is_existing_ledger_entry: bool = False,
523+
is_existing_transaction: bool = False
410524
):
411525
"""Validate that a section has required subsections."""
412526
# Get all subsections under this parent
@@ -424,8 +538,27 @@ def _validate_subsections(
424538
if section.level == parent_section.level + 1:
425539
subsections.append(section)
426540

541+
# For existing ledger entries, make certain subsections optional
542+
exempted_subsections = set()
543+
if is_existing_ledger_entry:
544+
exempted_subsections = {
545+
"Object Identifier",
546+
"Ownership",
547+
"Reserves",
548+
"Deletion",
549+
"RPC Name"
550+
}
551+
552+
# For existing transactions, make Transaction Fee optional
553+
if is_existing_transaction:
554+
exempted_subsections.add("Transaction Fee")
555+
427556
# Check for required subsections
428557
for sub_num, sub_title in required_subsections.items():
558+
# Skip if this subsection is exempted
559+
if sub_title in exempted_subsections:
560+
continue
561+
429562
# Find this subsection by title, regardless of its actual number
430563
found = any(
431564
sub_title in s.title

templates/AMENDMENT_TEMPLATE.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ _[If your specification introduces new ledger entry objects, document each entry
4242

4343
_[If your specification introduces new ledger entry common fields, you can have a section called `Transaction: Common Fields` before listing out any specific transactions.]_
4444

45+
_[**Note for existing ledger entries:** If you are documenting changes to an existing ledger entry type that is already deployed on the XRPL mainnet (e.g., AccountRoot, RippleState, Offer, etc.), the following subsections are **optional**: Ownership (2.4), Reserves (2.5), Deletion (2.6), and RPC Name (2.10). You only need to include these subsections if you are making changes to those aspects of the ledger entry.]_
46+
4547
### 2.1. Object Identifier
4648

4749
**Key Space:** `0x[XXXX]` _[Specify the 16-bit hex value for the key space]_
@@ -77,15 +79,17 @@ _[Detailed explanation of field behavior, validation rules, etc.]_
7779
| ---------------- | ------------ | ---------------- |
7880
| `[lsfFlagName1]` | `0x[Value1]` | `[Description1]` |
7981

80-
### 2.4. Ownership
82+
### 2.4. Ownership _(Optional if ledger entry already exists on XRPL)_
8183

8284
_[Specify which AccountRoot object owns this ledger entry and how the ownership relationship is established.]_
8385

8486
**Owner:** `[Account field name or "No owner" if this is a global object like FeeSettings]`
8587

8688
**Directory Registration:** `[Describe how this object is registered in the owner's directory, or specify if it's a special case]`
8789

88-
### 2.5. Reserves
90+
_[Note: This subsection is optional if you are documenting changes to an existing ledger entry type that is already deployed on the XRPL mainnet.]_
91+
92+
### 2.5. Reserves _(Optional if ledger entry already exists on XRPL)_
8993

9094
**Reserve Requirement:** `[Standard/Custom/None]`
9195

@@ -95,7 +99,9 @@ _[If Custom]: This ledger entry requires `[X]` reserve units because `[reason]`.
9599

96100
_[If None]: This ledger entry does not require additional reserves because `[reason]`._
97101

98-
### 2.6. Deletion
102+
_[Note: This subsection is optional if you are documenting changes to an existing ledger entry type that is already deployed on the XRPL mainnet.]_
103+
104+
### 2.6. Deletion _(Optional if ledger entry already exists on XRPL)_
99105

100106
**Deletion Transactions:** `[List transaction types that can delete this object]`
101107

@@ -109,6 +115,8 @@ _[If None]: This ledger entry does not require additional reserves because `[rea
109115
_[If Yes]: This object must be deleted before its owner account can be deleted._
110116
_[If No]: This object does not prevent its owner account from being deleted._
111117

118+
_[Note: This subsection is optional if you are documenting changes to an existing ledger entry type that is already deployed on the XRPL mainnet.]_
119+
112120
### 2.7. Pseudo-Account _(Optional)_
113121

114122
_[Only include this section if your ledger entry uses a pseudo-account. Otherwise, delete this subsection.]_
@@ -138,12 +146,14 @@ _[List logical statements that must always be true for this ledger entry. Use `<
138146
- `[Invariant 2, e.g., "IF <object>.Status == 'Active' THEN <object>.Account != NULL"]`
139147
- `[Additional invariants as needed]`
140148

141-
### 2.10. RPC Name
149+
### 2.10. RPC Name _(Optional if ledger entry already exists on XRPL)_
142150

143151
**RPC Type Name:** `[snake_case_name]`
144152

145153
_[This is the name used in `account_objects` and `ledger_data` RPC calls to filter for this object type]_
146154

155+
_[Note: This subsection is optional if you are documenting changes to an existing ledger entry type that is already deployed on the XRPL mainnet.]_
156+
147157
### 2.11. Example JSON
148158

149159
```json
@@ -165,6 +175,12 @@ _[If your specification introduces new transactions, document each transaction i
165175

166176
_[If your specification introduces new transaction common fields, you can have a section called `Transaction: Common Fields` before listing out any specific transactions.]_
167177

178+
> **Note for Existing Transactions:** If you are documenting changes to an existing transaction type (one that is already deployed on XRPL mainnet), the following subsection is **optional** unless you are modifying it:
179+
>
180+
> - **Transaction Fee** (3.3)
181+
>
182+
> For new transaction types, all subsections are required.
183+
168184
> **Naming Convention:** Transaction names should follow the pattern `<LedgerEntryName><Verb>` (e.g., `ExampleSet`, `ExampleDelete`). Most specifications will need at least:
169185
>
170186
> - `[Object]Set` or `[Object]Create`: Creates or updates the object
@@ -194,7 +210,9 @@ _[Detailed explanation of field behavior, validation rules, etc.]_
194210
| --------------- | ------------ | ---------------- |
195211
| `[tfFlagName1]` | `0x[Value1]` | `[Description1]` |
196212

197-
### 3.3. Transaction Fee
213+
### 3.3. Transaction Fee _(Optional if transaction already exists on XRPL)_
214+
215+
_[This subsection is optional if you are documenting changes to an existing transaction type. Only include it if you are introducing a new transaction type or modifying the fee structure of an existing one.]_
198216

199217
**Fee Structure:** `[Standard/Custom]`
200218

0 commit comments

Comments
 (0)