import re import os import sys def is_text_prop(tag_str): """Check if the NButton tag has the `text` prop (bare, :text, or v-bind:text).""" # Remove attribute values to avoid false positives cleaned = re.sub(r'"[^"]*"', '', tag_str) cleaned = re.sub(r"'[^']*'", '', cleaned) return bool(re.search(r'(?:^|[\s>])(?::text|v-bind:text|text)(?=[\s=>/]|$)', cleaned)) def has_dynamic_class(tag_str): """Check if the tag uses :class or v-bind:class (dynamic class binding).""" cleaned = re.sub(r'"[^"]*"', '', tag_str) cleaned = re.sub(r"'[^']*'", '', cleaned) return bool(re.search(r'(?::class|v-bind:class)\s*=', cleaned)) def has_skybtn_class(tag_str): """Check if the tag already has sky-btn in its class attribute value.""" for m in re.finditer(r'class\s*=\s*"([^"]*)"', tag_str): if 'sky-btn' in m.group(1): return True for m in re.finditer(r"class\s*=\s*'([^']*)'", tag_str): if 'sky-btn' in m.group(1): return True return False def add_skybtn_to_tag(tag_str): """Add class="sky-btn" or append sky-btn to existing class.""" # Check for existing class="..." m = re.search(r'class\s*=\s*"([^"]*)"', tag_str) if m: old_class = m.group(1).strip() new_class = (old_class + ' sky-btn') if old_class else 'sky-btn' return tag_str[:m.start(1)] + new_class + tag_str[m.end(1):] # Check for existing class='...' m = re.search(r"class\s*=\s*'([^']*)'", tag_str) if m: old_class = m.group(1).strip() new_class = (old_class + ' sky-btn') if old_class else 'sky-btn' return tag_str[:m.start(1)] + new_class + tag_str[m.end(1):] # No class attribute, add before closing > # Find the outermost > that closes the tag if tag_str.rstrip().endswith('/>'): insert_pos = tag_str.rstrip().rfind('/>') prefix = tag_str[:insert_pos].rstrip() suffix = tag_str[insert_pos:] return prefix + ' class="sky-btn" ' + suffix.lstrip() else: insert_pos = tag_str.rstrip().rfind('>') prefix = tag_str[:insert_pos].rstrip() suffix = tag_str[insert_pos:] return prefix + ' class="sky-btn"' + suffix def process_vue_file(filepath): try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() except Exception as e: print(f" Error reading {filepath}: {e}", file=sys.stderr) return False # Find the template section template_start_match = re.search(r'', content) if not template_end_match: return False template_start = template_start_match.start() template_end = template_end_match.end() template_section = content[template_start:template_end] before = content[:template_start] after = content[template_end:] # Match NButton/n-button opening tags with attribute-aware regex tag_pattern = r'<([Nn]-[Bb]utton)\b((?:[^>"\']|"[^"]*"|\'[^\']*\')*)\s*/?>' modified_sections = [] last_end = 0 modified_count = 0 for m in re.finditer(tag_pattern, template_section): modified_sections.append(template_section[last_end:m.start()]) full_match = m.group(0) if is_text_prop(full_match): modified_sections.append(full_match) elif has_dynamic_class(full_match): modified_sections.append(full_match) elif has_skybtn_class(full_match): modified_sections.append(full_match) else: new_tag = add_skybtn_to_tag(full_match) modified_sections.append(new_tag) modified_count += 1 last_end = m.end() modified_sections.append(template_section[last_end:]) if modified_count > 0: new_template_section = ''.join(modified_sections) new_content = before + new_template_section + after with open(filepath, 'w', encoding='utf-8') as f: f.write(new_content) print(f" Modified ({modified_count} changes): {os.path.relpath(filepath, os.getcwd())}") return True return False def main(): cwd = os.getcwd() print(f"Working directory: {cwd}") vue_files = [] for root, dirs, files in os.walk(os.path.join(cwd, 'frontend', 'src')): for f in files: if f.endswith('.vue'): vue_files.append(os.path.join(root, f)) print(f"Found {len(vue_files)} Vue files") changed_files = [] for filepath in sorted(vue_files): if process_vue_file(filepath): changed_files.append(filepath) print(f"\nTotal files modified: {len(changed_files)}") for fp in changed_files: print(f" {os.path.relpath(fp, cwd)}") return len(changed_files) if __name__ == '__main__': main()