Skip to content

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 project 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 project 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 project.

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 project."""
    if not _is_valid_project_model_path(path):
        raise err.TrestleError(f'Trestle project 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 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 project 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 project 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)

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