2023-11-03 11:02:53 +08:00

122 lines
5.1 KiB
Python

import os
from typing import List, Optional
import re
class Namespace:
def __init__(self, root_path: str,
branches: List[str] = None):
"""
:param root_path: The root path of the namespace.
:param branches: The hidden branches with ascending order of priority.
"""
self.root_path = root_path
self.branches = branches if branches else ['sys', 'org', 'usr']
@staticmethod
def is_valid_name(name: str) -> bool:
"""
Check if a name is valid.
A valid name is either an empty string or
a sequence of one or more alphanumeric characters, hyphens, or underscores,
separated by single dots. Each component cannot contain a dot.
:param name: The name to check.
:return: True if the name is valid, False otherwise.
"""
# The regular expression pattern for a valid name
if name is None:
return False
pattern = r'^$|^(?!.*\.\.)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*$'
return bool(re.match(pattern, name))
def get_file(self, name: str, file: str) -> Optional[str]:
"""
:param name: The command name in the namespace.
:param file: The target file name.
:return: The full path of the target file in the command directory.
"""
if not self.is_valid_name(name):
return None
# Convert the dot-separated name to a path
path = os.path.join(*name.split('.'))
for branch in reversed(self.branches):
full_path = os.path.join(self.root_path, branch, path)
if os.path.isdir(full_path):
# If it exists and is a directory, check for the file
file_path = os.path.join(full_path, file)
if os.path.isfile(file_path):
# If the file exists, return its path
return file_path
# If no file is found, return None
return None
def list_files(self, name: str) -> List[str]:
"""
:param name: The command name in the namespace.
:return: The full paths of the files in the command directory.
"""
if not self.is_valid_name(name):
raise ValueError(f"Invalid name to list files: {name}")
# Convert the dot-separated name to a path
path = os.path.join(*name.split('.'))
files = {}
path_found = False
for branch in self.branches:
full_path = os.path.join(self.root_path, branch, path)
if os.path.isdir(full_path):
# If it exists and is a directory, get the files
path_found = True
for file in os.listdir(full_path):
files[file] = os.path.join(full_path, file)
# If no existing path is found, raise an error
if not path_found:
raise ValueError(f"Path not found to list files: {name}")
# If path is found but no files exist, return an empty list
# Sort the files in alphabetical order before returning
return sorted(files.values()) if files else []
def list_names(self, name: str = '', recursive: bool = False) -> List[str]:
"""
:param name: The command name in the namespace. Defaults to the root.
:param recursive: Whether to list all descendant names or only child names.
:return: A list of all names under the given name.
"""
if not self.is_valid_name(name):
raise ValueError(f"Invalid name to list names: {name}")
commands = set()
path = os.path.join(*name.split('.'))
found = False
for branch in self.branches:
full_path = os.path.join(self.root_path, branch, path)
if os.path.isdir(full_path):
found = True
self._add_dirnames_to_commands(full_path, name, commands)
if recursive:
self._add_recursive_dirnames_to_commands(full_path, name, commands)
if not found:
raise ValueError(f"Path not found to list names: '{name}'")
return sorted(commands)
def _add_dirnames_to_commands(self, full_path: str, name: str, commands: set):
for dirname in os.listdir(full_path):
if dirname.startswith('.'):
continue
if os.path.isdir(os.path.join(full_path, dirname)):
command_name = '.'.join([name, dirname]) if name else dirname
commands.add(command_name)
def _add_recursive_dirnames_to_commands(self, full_path: str, name: str, commands: set):
self._recursive_dir_walk(full_path, name, commands)
def _recursive_dir_walk(self, full_path: str, name: str, commands: set):
for dirname in os.listdir(full_path):
if dirname.startswith('.'):
continue
dir_path = os.path.join(full_path, dirname)
if os.path.isdir(dir_path):
command_name = '.'.join([name, dirname]) if name else dirname
commands.add(command_name)
self._recursive_dir_walk(dir_path, command_name, commands)