Skip to content

Instantly share code, notes, and snippets.

@hlord2000
Created November 1, 2024 15:24
Show Gist options
  • Save hlord2000/7782e6fab2854f8df12bf859b564f135 to your computer and use it in GitHub Desktop.
Save hlord2000/7782e6fab2854f8df12bf859b564f135 to your computer and use it in GitHub Desktop.
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