file_utils
trestle.common.file_utils
¤
Trestle file system utils.
logger
¤
Functions¤
check_oscal_directories(root_path)
¤
Identify the state of the trestle workspace.
Traverses trestle workspace and looks for unexpected files or directories. Additional files are allowed in the Trestle root but not inside the model folders.
Source code in trestle/common/file_utils.py
def check_oscal_directories(root_path: pathlib.Path) -> bool:
"""
Identify the state of the trestle workspace.
Traverses trestle workspace and looks for unexpected files or directories.
Additional files are allowed in the Trestle root but not inside the model folders.
"""
trestle_dir_walk = os.walk(root_path)
is_valid = True
for _, dirs, _ in trestle_dir_walk:
for d in dirs:
if d in MODEL_DIR_LIST:
is_valid = _verify_oscal_folder(root_path / d)
if not is_valid:
break
return is_valid
extract_project_model_path(path)
¤
Get the base path of the trestle model project.
Source code in trestle/common/file_utils.py
def extract_project_model_path(path: pathlib.Path) -> Optional[pathlib.Path]:
"""Get the base path of the trestle model project."""
if len(path.parts) > 2:
for i in range(2, len(path.parts)):
current = pathlib.Path(path.parts[0]).joinpath(*path.parts[1:i + 1])
if _is_valid_project_model_path(current):
return current
return None
extract_trestle_project_root(path)
¤
Get the trestle workspace root folder in the path.
Source code in trestle/common/file_utils.py
def extract_trestle_project_root(path: pathlib.Path) -> Optional[pathlib.Path]:
"""Get the trestle workspace root folder in the path."""
while len(path.parts) > 1: # it must not be the system root directory
if is_valid_project_root(path):
return path
path = path.parent
return None
get_contextual_file_type(path)
¤
Return the file content type for files in the given directory, if it's a trestle workspace.
Source code in trestle/common/file_utils.py
def get_contextual_file_type(path: pathlib.Path) -> FileContentType:
"""Return the file content type for files in the given directory, if it's a trestle workspace."""
if not _is_valid_project_model_path(path):
raise err.TrestleError(f'Trestle workspace not found at path {path}')
for file_or_directory in iterdir_without_hidden_files(path):
if file_or_directory.is_file():
return FileContentType.to_content_type(file_or_directory.suffix)
for file_or_directory in path.iterdir():
if file_or_directory.is_dir():
return get_contextual_file_type(file_or_directory)
raise err.TrestleError('No files found in the project.')
insert_text_in_file(file_path, tag, text)
¤
Insert text lines after line containing tag.
Return True on success, False tag not found. Text is a string with appropriate \n line endings. If tag is none just add at end of file. This will only open file once if tag is not found.
Source code in trestle/common/file_utils.py
def insert_text_in_file(file_path: pathlib.Path, tag: Optional[str], text: str) -> bool:
r"""Insert text lines after line containing tag.
Return True on success, False tag not found.
Text is a string with appropriate \n line endings.
If tag is none just add at end of file.
This will only open file once if tag is not found.
"""
if not file_path.exists():
raise TrestleError(f'Test file {file_path} not found.')
if tag:
lines: List[str] = []
with file_path.open('r', encoding=const.FILE_ENCODING) as f:
lines = f.readlines()
for ii, line in enumerate(lines):
if line.find(tag) >= 0:
lines.insert(ii + 1, text)
with file_path.open('w', encoding=const.FILE_ENCODING) as f:
f.writelines(lines)
return True
else:
with file_path.open('a', encoding=const.FILE_ENCODING) as f:
f.writelines(text)
return True
return False
is_directory_name_allowed(name)
¤
Determine whether a directory name, which is a 'non-core-OSCAL activity/directory is allowed.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
the name which is assumed may take the form of a relative path for task/subtasks. |
required |
Returns:
Type | Description |
---|---|
bool |
Whether the name is allowed or not allowed (interferes with assumed project directories such as catalogs). |
Source code in trestle/common/file_utils.py
def is_directory_name_allowed(name: str) -> bool:
"""Determine whether a directory name, which is a 'non-core-OSCAL activity/directory is allowed.
args:
name: the name which is assumed may take the form of a relative path for task/subtasks.
Returns:
Whether the name is allowed or not allowed (interferes with assumed project directories such as catalogs).
"""
# Task must not use an OSCAL directory
# Task must not self-interfere with a project
pathed_name = pathlib.Path(name)
root_path = pathed_name.parts[0]
if root_path in const.MODEL_TYPE_TO_MODEL_DIR.values():
logger.warning('Task name is the same as an OSCAL schema name.')
return False
if root_path[0] == '.':
logger.warning('Task name must not start with "."')
return False
if pathed_name.suffix != '':
# Does it look like a file
logger.warning('Task name must not look like a file path (e.g. contain a suffix')
return False
if '__global__' in pathed_name.parts:
logger.warning('Task name cannot contain __global__')
return False
return True
is_hidden(file_path)
¤
Determine whether a file is hidden based on the appropriate os attributes.
This function will only work for the current file path only (e.g. not if a parent is hidden).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
file_path |
Path |
The file path for which we are testing whether the file / directory is hidden. |
required |
Returns:
Type | Description |
---|---|
bool |
Whether or not the file is file/directory is hidden. |
Source code in trestle/common/file_utils.py
def is_hidden(file_path: pathlib.Path) -> bool:
"""
Determine whether a file is hidden based on the appropriate os attributes.
This function will only work for the current file path only (e.g. not if a parent is hidden).
Args:
file_path: The file path for which we are testing whether the file / directory is hidden.
Returns:
Whether or not the file is file/directory is hidden.
"""
# as far as trestle is concerned all .* files are hidden even on windows, regardless of attributes
if file_path.stem.startswith('.'):
return True
# Handle windows
if is_windows(): # pragma: no cover
attribute = win32api.GetFileAttributes(str(file_path))
return attribute & (win32con.FILE_ATTRIBUTE_HIDDEN | win32con.FILE_ATTRIBUTE_SYSTEM)
return False
is_local_and_visible(file_path)
¤
Is the file or dir local (not a symlink) and not hidden.
Source code in trestle/common/file_utils.py
def is_local_and_visible(file_path: pathlib.Path) -> bool:
"""Is the file or dir local (not a symlink) and not hidden."""
return not (is_hidden(file_path) or is_symlink(file_path))
is_symlink(file_path)
¤
Is the file path a symlink.
Source code in trestle/common/file_utils.py
def is_symlink(file_path: pathlib.Path) -> bool:
"""Is the file path a symlink."""
if is_windows():
return file_path.suffix == '.lnk'
return file_path.is_symlink()
is_valid_project_root(path)
¤
Check if the path is a valid trestle workspace root.
Source code in trestle/common/file_utils.py
def is_valid_project_root(path: pathlib.Path) -> bool:
"""Check if the path is a valid trestle workspace root."""
trestle_dir = path / const.TRESTLE_CONFIG_DIR
return trestle_dir.exists() and trestle_dir.is_dir()
is_windows()
¤
Check if current operating system is Windows.
Source code in trestle/common/file_utils.py
def is_windows() -> bool:
"""Check if current operating system is Windows."""
return platform.system() == const.WINDOWS_PLATFORM_STR
iterdir_without_hidden_files(directory_path)
¤
Get iterator over all paths in the given directory_path excluding hidden files.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
directory_path |
Path |
The directory to iterate through. |
required |
Returns:
Type | Description |
---|---|
Iterable[pathlib.Path] |
Iterator over the files in the directory excluding hidden files. |
Source code in trestle/common/file_utils.py
def iterdir_without_hidden_files(directory_path: pathlib.Path) -> Iterable[pathlib.Path]:
"""
Get iterator over all paths in the given directory_path excluding hidden files.
Args:
directory_path: The directory to iterate through.
Returns:
Iterator over the files in the directory excluding hidden files.
"""
filtered_paths = list(filter(lambda p: not is_hidden(p) or p.is_dir(), pathlib.Path.iterdir(directory_path)))
return filtered_paths.__iter__()
load_file(file_path)
¤
Load JSON or YAML file content into a dict.
This is not intended to be the default load mechanism. It should only be used if a OSCAL object type is unknown but the context a user is in.
Source code in trestle/common/file_utils.py
def load_file(file_path: pathlib.Path) -> Dict[str, Any]:
"""
Load JSON or YAML file content into a dict.
This is not intended to be the default load mechanism. It should only be used
if a OSCAL object type is unknown but the context a user is in.
"""
content_type = FileContentType.to_content_type(file_path.suffix)
with file_path.open('r', encoding=const.FILE_ENCODING) as f:
if content_type == FileContentType.YAML:
yaml = YAML(typ='safe')
return yaml.load(f)
if content_type == FileContentType.JSON:
return json.load(f)
return {}
make_hidden_file(file_path)
¤
Make hidden file.
Source code in trestle/common/file_utils.py
def make_hidden_file(file_path: pathlib.Path) -> None:
"""Make hidden file."""
if not file_path.name.startswith('.') and not is_windows():
file_path = file_path.parent / ('.' + file_path.name)
file_path.touch()
if is_windows():
atts = win32api.GetFileAttributes(str(file_path))
win32api.SetFileAttributes(str(file_path), win32con.FILE_ATTRIBUTE_HIDDEN | atts)
prune_empty_dirs(file_path, pattern)
¤
Remove directories with no subdirs and with no files matching pattern.
Source code in trestle/common/file_utils.py
def prune_empty_dirs(file_path: pathlib.Path, pattern: str) -> None:
"""Remove directories with no subdirs and with no files matching pattern."""
deleted: Set[str] = set()
# this traverses from leaf nodes upward so only needs one traversal
for current_dir, subdirs, _ in os.walk(str(file_path), topdown=False):
true_dirs = [subdir for subdir in subdirs if os.path.join(current_dir, subdir) not in deleted]
if not true_dirs and not any(glob.glob(f'{current_dir}/{pattern}')):
shutil.rmtree(current_dir)
deleted.add(current_dir)
relative_resolve(candidate, cwd)
¤
Resolve a candidate file path relative to a provided cwd.
This is to circumvent bad behaviour for resolve on windows platforms where the path must exist.
If a relative dir is passed it presumes the directory is relative to the PROVIDED cwd. If relative expansions exist (e.g. ../) the final result must still be within the cwd.
If an absolute path is provided it tests whether the path is within the cwd or not.
Source code in trestle/common/file_utils.py
def relative_resolve(candidate: pathlib.Path, cwd: pathlib.Path) -> pathlib.Path:
"""Resolve a candidate file path relative to a provided cwd.
This is to circumvent bad behaviour for resolve on windows platforms where the path must exist.
If a relative dir is passed it presumes the directory is relative to the PROVIDED cwd.
If relative expansions exist (e.g. ../) the final result must still be within the cwd.
If an absolute path is provided it tests whether the path is within the cwd or not.
"""
# Expand user first if applicable.
candidate = candidate.expanduser()
if not cwd.is_absolute():
raise TrestleError('Error handling current working directory. CWD is expected to be absolute.')
if not candidate.is_absolute():
new = pathlib.Path(cwd / candidate).resolve()
else:
new = candidate.resolve()
try:
new.relative_to(cwd)
except ValueError:
raise TrestleError(f'Provided dir {candidate} is not relative to {cwd}')
return new
handler: python