Created
November 1, 2024 15:24
-
-
Save hlord2000/7782e6fab2854f8df12bf859b564f135 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import os | |
from pathlib import Path | |
import shutil | |
import sys | |
from west import log | |
from west.commands import WestCommand | |
from InquirerPy import inquirer | |
class BoardCreator(WestCommand): | |
def __init__(self): | |
super().__init__( | |
'board-create', | |
'Create a new board based on an existing one', | |
'Create a new board by copying and renaming files from an existing board', | |
accepts_unknown_args=False) | |
self.boards_root = None | |
self.board_vendors = {} | |
def do_add_parser(self, parser_adder): | |
parser = parser_adder.add_parser( | |
self.name, | |
help=self.help, | |
description=self.description) | |
parser.add_argument('--board', '-b', | |
help='Source board to copy from') | |
parser.add_argument('--target', '-t', | |
help='New board name') | |
parser.add_argument('--list-boards', '-l', | |
action='store_true', | |
help='List available boards') | |
parser.add_argument('--vendor', '-v', | |
help='Filter boards by vendor') | |
parser.add_argument('--non-interactive', '-n', | |
action='store_true', | |
help='Non-interactive mode, requires --board and --target') | |
return parser | |
def _find_boards(self): | |
"""Find all available boards and organize by vendor.""" | |
if not self.boards_root: | |
zephyr_base = os.getenv('ZEPHYR_BASE') | |
if not zephyr_base: | |
log.err('ZEPHYR_BASE environment variable not set') | |
sys.exit(1) | |
self.boards_root = Path(zephyr_base) / 'boards' | |
# Clear existing data | |
self.board_vendors = {} | |
# Scan board directories | |
for vendor_dir in self.boards_root.iterdir(): | |
if vendor_dir.is_dir(): | |
vendor = vendor_dir.name | |
boards = [] | |
for board_dir in vendor_dir.iterdir(): | |
if board_dir.is_dir(): | |
boards.append(board_dir.name) | |
if boards: | |
self.board_vendors[vendor] = sorted(boards) | |
def _list_boards(self, vendor=None): | |
"""Display available boards, optionally filtered by vendor.""" | |
if not self.board_vendors: | |
self._find_boards() | |
if vendor: | |
if vendor in self.board_vendors: | |
log.inf(f"\nBoards for vendor '{vendor}':") | |
for board in self.board_vendors[vendor]: | |
log.inf(f" - {board}") | |
else: | |
log.wrn(f"No boards found for vendor '{vendor}'") | |
else: | |
log.inf("\nAvailable vendors and boards:") | |
for vendor, boards in self.board_vendors.items(): | |
log.inf(f"\n{vendor}:") | |
for board in boards: | |
log.inf(f" - {board}") | |
def _validate_board_name(self, name): | |
"""Validate the new board name.""" | |
if not name: | |
return False, "Board name cannot be empty" | |
if not name.replace('-', '').replace('_', '').isalnum(): | |
return False, "Board name can only contain letters, numbers, hyphens, and underscores" | |
return True, "" | |
def _interactive_select_vendor(self): | |
"""Interactive vendor selection with FZF-like interface.""" | |
vendors = sorted(self.board_vendors.keys()) | |
try: | |
vendor = inquirer.fuzzy( | |
message="Select a vendor:", | |
choices=vendors, | |
cycle=True, | |
border=True, | |
height=20, | |
instruction="(Type to filter)" | |
).execute() | |
if vendor is None: # User pressed Ctrl+C | |
log.err("\nOperation cancelled") | |
sys.exit(1) | |
return vendor | |
except KeyboardInterrupt: | |
log.err("\nOperation cancelled") | |
sys.exit(1) | |
def _interactive_select_board(self, vendor): | |
"""Interactive board selection with FZF-like interface.""" | |
boards = sorted(self.board_vendors[vendor]) | |
try: | |
board = inquirer.fuzzy( | |
message=f"Select a board from {vendor}:", | |
choices=boards, | |
cycle=True, | |
border=True, | |
height=20, | |
instruction="(Type to filter)" | |
).execute() | |
if board is None: # User pressed Ctrl+C | |
log.err("\nOperation cancelled") | |
sys.exit(1) | |
return board | |
except KeyboardInterrupt: | |
log.err("\nOperation cancelled") | |
sys.exit(1) | |
def _get_target_board_name(self): | |
"""Get and validate target board name.""" | |
while True: | |
try: | |
name = inquirer.text( | |
message="Enter new board name:", | |
validate=lambda text: True if self._validate_board_name(text)[0] else "Invalid board name", | |
).execute() | |
if name is None: # User pressed Ctrl+C | |
log.err("\nOperation cancelled") | |
sys.exit(1) | |
return name | |
except KeyboardInterrupt: | |
log.err("\nOperation cancelled") | |
sys.exit(1) | |
def _copy_and_rename_board(self, src_board, target_board, vendor): | |
"""Copy board files and rename contents.""" | |
src_path = self.boards_root / vendor / src_board | |
if not src_path.exists(): | |
log.err(f"Source board '{src_board}' not found in vendor '{vendor}' directory") | |
return False | |
# Create target directory | |
dst_path = Path.cwd() / target_board | |
if dst_path.exists(): | |
log.err(f"Target directory '{target_board}' already exists") | |
return False | |
# Copy directory | |
shutil.copytree(src_path, dst_path) | |
total_replacements = 0 | |
modified_files = 0 | |
# Rename files first | |
for root, _, files in os.walk(dst_path): | |
for filename in files: | |
old_path = Path(root) / filename | |
new_filename = filename | |
# Do the exact case-sensitive replacements in filename | |
if src_board.lower() in new_filename: | |
new_filename = new_filename.replace(src_board.lower(), target_board.lower()) | |
if src_board.upper() in new_filename: | |
new_filename = new_filename.replace(src_board.upper(), target_board.upper()) | |
if new_filename != filename: | |
new_path = Path(root) / new_filename | |
old_path.rename(new_path) | |
log.dbg(f"Renamed: {filename} -> {new_filename}") | |
else: | |
new_path = old_path | |
# Update file contents | |
try: | |
with open(new_path, 'r', encoding='utf-8') as f: | |
content = f.read() | |
modified = False | |
new_content = content | |
# Do the exact case-sensitive replacements in content | |
if src_board.lower() in new_content: | |
count = new_content.count(src_board.lower()) | |
new_content = new_content.replace(src_board.lower(), target_board.lower()) | |
total_replacements += count | |
modified = True | |
if src_board.upper() in new_content: | |
count = new_content.count(src_board.upper()) | |
new_content = new_content.replace(src_board.upper(), target_board.upper()) | |
total_replacements += count | |
modified = True | |
if modified: | |
with open(new_path, 'w', encoding='utf-8') as f: | |
f.write(new_content) | |
modified_files += 1 | |
except UnicodeDecodeError: | |
log.dbg(f"Skipping binary file: {new_path.name}") | |
continue | |
log.inf(f"\nReplacement Statistics:") | |
log.inf(f"- Modified {modified_files} files") | |
log.inf(f"- Made {total_replacements} total replacements") | |
return True | |
def do_run(self, args, _): | |
self._find_boards() | |
if args.list_boards: | |
self._list_boards(args.vendor) | |
return | |
if args.non_interactive and (not args.board or not args.target): | |
log.err("Non-interactive mode requires both --board and --target arguments") | |
return | |
if not args.non_interactive and (not args.board or not args.target): | |
# Interactive mode with FZF-like interface | |
log.inf("\nBoard Creator - Interactive Mode") | |
# Select vendor using FZF-like interface | |
vendor = self._interactive_select_vendor() | |
# Select board using FZF-like interface | |
src_board = self._interactive_select_board(vendor) | |
# Get target board name with validation | |
target_board = self._get_target_board_name() | |
else: | |
src_board = args.board | |
target_board = args.target | |
# Find vendor for the source board | |
vendor = None | |
for v, boards in self.board_vendors.items(): | |
if src_board in boards: | |
vendor = v | |
break | |
if not vendor: | |
log.err(f"Board '{src_board}' not found in any vendor directory") | |
return | |
log.inf(f"\nCreating new board '{target_board}' based on '{src_board}'...") | |
if self._copy_and_rename_board(src_board, target_board, vendor): | |
log.inf(f"\nSuccessfully created new board '{target_board}'!") | |
log.inf(f"Board files are in the '{target_board}' directory") | |
else: | |
log.err("Failed to create new board") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment