Skip to content

Instantly share code, notes, and snippets.

@ashwch
Last active July 13, 2025 19:23
Show Gist options
  • Select an option

  • Save ashwch/cd87eb1574b1b88d21ddef1508a186f6 to your computer and use it in GitHub Desktop.

Select an option

Save ashwch/cd87eb1574b1b88d21ddef1508a186f6 to your computer and use it in GitHub Desktop.
Bruno environment file validator - Validate .bru files for correct format
#!/usr/bin/env python3
# /// script
# dependencies = []
# ///
"""
Bruno environment file validator.
Validates .bru files for correct Bruno API client format.
Usage:
python validate_bruno_files.py <file_or_directory>
Examples:
python validate_bruno_files.py ./environments/
python validate_bruno_files.py ./env.bru
"""
import sys
from pathlib import Path
import re
def validate_bruno_file(file_path):
"""
Validate a Bruno .bru environment file format.
Returns:
tuple: (is_valid: bool, issues: list[str])
"""
issues = []
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
return False, [f"Cannot read file: {e}"]
# Check for empty file
if not content.strip():
return False, ["File is empty"]
# Check for meta block with name field
meta_pattern = r'meta\s*\{[^}]*name:\s*[\w\-\.]+[^}]*\}'
if not re.search(meta_pattern, content, re.DOTALL):
issues.append("Missing or invalid 'meta' block with 'name' field")
# Check for vars block
vars_pattern = r'vars\s*\{[^}]*\}'
if not re.search(vars_pattern, content, re.DOTALL):
issues.append("Missing 'vars' block")
# Validate vars block content
# Find the vars block more carefully to handle escaped braces
vars_start = content.find('vars')
if vars_start != -1:
# Find the opening brace
open_brace = content.find('{', vars_start)
if open_brace != -1:
# Find matching closing brace, accounting for escaped braces
brace_count = 1
i = open_brace + 1
while i < len(content) and brace_count > 0:
if content[i] == '\\' and i + 1 < len(content):
i += 2 # Skip escaped character
continue
elif content[i] == '{':
brace_count += 1
elif content[i] == '}':
brace_count -= 1
i += 1
if brace_count == 0:
vars_content = content[open_brace + 1:i - 1]
vars_match = True
else:
vars_match = None
vars_content = None
else:
vars_match = None
else:
vars_match = None
if vars_match and vars_content:
lines = [line.strip() for line in vars_content.strip().split('\n') if line.strip()]
for line_num, line in enumerate(lines, 1):
# Skip empty lines
if not line:
continue
# Check for key: value format
if ':' not in line:
issues.append(f"Invalid variable format at line ~{line_num}: '{line[:30]}...' (missing colon)")
continue
# Split only on first colon to handle values with colons
key, value = line.split(':', 1)
key = key.strip()
value = value.strip()
# Validate key format (alphanumeric, underscore, hyphen)
if not re.match(r'^[\w\-]+$', key):
issues.append(f"Invalid variable name '{key}' (use only letters, numbers, underscore, hyphen)")
# Check for proper quote handling in values
if value:
# For quoted strings, check if properly wrapped
if value.startswith('"'):
if not value.endswith('"'):
issues.append(f"Unclosed quoted value in variable '{key}'")
elif len(value) > 1 and value.endswith('\\"'):
# Check if the ending quote is escaped
issues.append(f"Value ends with escaped quote in variable '{key}'")
elif value.endswith('"') and not value.startswith('"'):
issues.append(f"Value has closing quote but no opening quote in variable '{key}'")
# Check overall brace balance (excluding escaped braces)
temp_content = content.replace('\\{', '').replace('\\}', '')
open_braces = temp_content.count('{')
close_braces = temp_content.count('}')
if open_braces != close_braces:
issues.append(f"Unbalanced braces in file: {open_braces} open vs {close_braces} close")
# Check for required structure
if open_braces < 2 or close_braces < 2:
issues.append("File must contain at least meta{} and vars{} blocks")
return len(issues) == 0, issues
def main():
"""Validate Bruno .bru files."""
if len(sys.argv) < 2:
print("Usage: python validate_bruno_files.py <file_or_directory>")
print("\nExamples:")
print(" Single file: python validate_bruno_files.py env.bru")
print(" Directory: python validate_bruno_files.py ./environments/")
sys.exit(1)
path = Path(sys.argv[1])
# Determine if validating single file or directory
if path.is_file():
if not path.suffix == '.bru':
print(f"❌ Error: {path} is not a .bru file")
sys.exit(1)
bru_files = [path]
elif path.is_dir():
bru_files = list(path.glob("*.bru"))
if not bru_files:
print(f"❌ No .bru files found in {path}")
sys.exit(1)
else:
print(f"❌ Error: {path} does not exist")
sys.exit(1)
# Validate files
print(f"\nπŸ” Validating {len(bru_files)} Bruno file(s)...\n")
valid_count = 0
invalid_files = []
for bru_file in sorted(bru_files):
is_valid, issues = validate_bruno_file(bru_file)
if is_valid:
print(f"βœ… {bru_file.name}")
valid_count += 1
else:
print(f"❌ {bru_file.name}")
invalid_files.append((bru_file.name, issues))
for issue in issues[:3]: # Show first 3 issues
print(f" └─ {issue}")
if len(issues) > 3:
print(f" └─ ... and {len(issues) - 3} more issue(s)")
# Summary
print(f"\nπŸ“Š Summary: {valid_count}/{len(bru_files)} file(s) valid")
if invalid_files and len(bru_files) <= 5:
print("\nπŸ”§ Fix suggestions:")
for filename, issues in invalid_files:
print(f"\n{filename}:")
for issue in issues:
print(f" β€’ {issue}")
return valid_count == len(bru_files)
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment