-
Notifications
You must be signed in to change notification settings - Fork 3
Add path_report capability to help observe file and directory challenges
#42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
a3f9459
Create check.py
d33bs caca551
path report capabilities
d33bs 9a3c44b
linting
d33bs f204e58
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] 9b96f8f
remove pull_request set of testion actions
d33bs 35958e7
Update .pre-commit-config.yaml
d33bs e6648dc
Update .pre-commit-config.yaml
d33bs 4ec871e
use pull request src instead of target for chkout
d33bs 39628cc
add debug for actions
d33bs 53348d5
remove debug
d33bs 70e4975
Merge remote-tracking branch 'upstream/main' into file-and-folder-issues
d33bs 9d9acf8
update to avoid coverage discrepancies
d33bs f5857fa
Merge remote-tracking branch 'upstream/main' into file-and-folder-issues
d33bs 6105909
Update coverage-badge.svg
d33bs 7b058ec
move to macos for pre-commit checks
d33bs 3ddb813
linting
d33bs 5c2c49e
Merge remote-tracking branch 'upstream/main' into file-and-folder-issues
d33bs 1381462
ignore tests for coverage
d33bs 742b3f9
remove coverage reporting
d33bs d7ff111
remove shellcheck because of docker
d33bs 3f8edfd
add path reporter to cli
d33bs 832d881
update for return code expectations
d33bs ec9fd56
add complete test data
d33bs b347a76
update for tests and readd coverage just in case
d33bs 782cf4f
test locally
d33bs cc03826
Update run-tests.yml
d33bs e975d27
Update coverage-badge.svg
d33bs 486f950
Update coverage-badge.svg
d33bs a93ccf1
readd coverage
d33bs 88272b5
Update .pre-commit-config.yaml
d33bs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| """ | ||
| Module to analyze local file paths and return structured information. | ||
| """ | ||
|
|
||
| import pathlib | ||
| from collections import Counter | ||
| from difflib import SequenceMatcher | ||
| from typing import Dict, List, Optional, Union | ||
|
|
||
| paths_type = Dict[str, Union[str, bool, int, float, None]] | ||
|
|
||
|
|
||
| def get_path_info(base_path: str, ignore: Optional[List[str]] = None) -> paths_type: | ||
| """ | ||
| Takes a local path and returns a data structure with | ||
| details about the path. | ||
|
|
||
| Args: | ||
| base_path (str): | ||
| The local path to analyze. | ||
| ignore (Optional[List[str]]): | ||
| A list of patterns to ignore | ||
| (e.g., hidden files, specific file types). | ||
|
|
||
| Returns: | ||
| paths_type: | ||
| A dictionary containing the filepath, | ||
| filename, type (file/dir), extension (if file), | ||
| hidden status, and filesize. | ||
| """ | ||
|
|
||
| if not (base_path := pathlib.Path(base_path)).exists(): | ||
| raise FileNotFoundError(f"The path '{base_path}' does not exist.") | ||
|
|
||
| path_info = [ | ||
| { | ||
| "filepath": str(path.resolve()), | ||
| "filename": path.name, | ||
| "filepath_parent": str(path.parent.resolve()), | ||
| "type": "directory" if path.is_dir() else "file", | ||
| "extension": path.suffix if path.is_file() else None, | ||
| "hidden": path.name.startswith("."), | ||
| "filesize": path.stat().st_size if path.is_file() else None, | ||
| } | ||
| for path in base_path.rglob("*") | ||
| if ignore is None | ||
| or ( | ||
| path not in ignore | ||
| and not any(str(path).startswith(ignore_path) for ignore_path in ignore) | ||
| and not any(str(path).endswith(ignore_path) for ignore_path in ignore) | ||
| ) | ||
| ] | ||
|
|
||
| return path_info | ||
|
|
||
|
|
||
| def find_empty_directories(paths: paths_type) -> paths_type: | ||
| """ | ||
| Recursively finds empty directories starting | ||
| from the given base path. | ||
|
|
||
| Args: | ||
| paths (paths_type): | ||
| The base path to start searching | ||
| for empty directories. | ||
|
|
||
| Returns: | ||
| list: | ||
| A list of paths to empty directories. | ||
| """ | ||
|
|
||
| directory_children = Counter(path["filepath_parent"] for path in paths) | ||
|
|
||
| return [ | ||
| path["filepath"] | ||
| for path in paths | ||
| if path["type"] == "directory" and directory_children[path["filepath"]] == 0 | ||
| ] | ||
|
|
||
|
|
||
| def count_file_extensions(paths: paths_type) -> Dict[str, int]: | ||
| """ | ||
| Counts all file extensions in the given paths | ||
| and returns the count per file extension. | ||
|
|
||
| Args: | ||
| paths (paths_type): | ||
| The paths data structure containing file information. | ||
|
|
||
| Returns: | ||
| Dict[str, int]: | ||
| A dictionary with file extensions | ||
| as keys and their counts as values. | ||
| """ | ||
| return Counter( | ||
| path["extension"] | ||
| for path in paths | ||
| if path["type"] == "file" and path["extension"] | ||
| ) | ||
|
|
||
|
|
||
| def find_similar_directories( | ||
| paths: paths_type, similarity_threshold: float = 0.97 | ||
| ) -> List[tuple]: | ||
| """ | ||
| Finds pairs of directory names that | ||
| are extremely similar to each other. | ||
|
|
||
| Args: | ||
| paths (paths_type): | ||
| The paths data structure containing | ||
| file and directory information. | ||
| similarity_threshold (float): | ||
| The threshold above which two | ||
| directory names are considered similar. | ||
|
|
||
| Returns: | ||
| List[tuple]: | ||
| A list of tuples containing pairs of | ||
| similar directory names. | ||
| """ | ||
|
|
||
| directories = [path["filepath"] for path in paths if path["type"] == "directory"] | ||
| similar_pairs = [] | ||
|
|
||
| for i, dir1 in enumerate(directories): | ||
| for dir2 in directories[i + 1 :]: | ||
| similarity = SequenceMatcher(None, dir1, dir2).ratio() | ||
| if similarity >= similarity_threshold: | ||
| similar_pairs.append((dir1, dir2)) | ||
|
|
||
| return similar_pairs | ||
|
|
||
|
|
||
| def path_report( | ||
| base_path: str, ignore: Optional[List[str]] = None, print_report: bool = False | ||
| ) -> Dict[str, Union[Dict[str, int], List[str], List[tuple]]]: | ||
| """ | ||
| Generates a report of the local file paths. | ||
|
|
||
| Args: | ||
| base_path (str): | ||
| The base path to analyze. | ||
| ignore (Optional[List[str]]): | ||
| A list of patterns to ignore which is sent to | ||
| the get_path_info function. (e.g., | ||
| hidden files, specific file types). | ||
| print_report (bool): | ||
| Whether to print the report to the screen. | ||
|
|
||
| Returns: | ||
| dict: | ||
| A dictionary containing the | ||
| path information and empty directories. | ||
| """ | ||
|
|
||
| paths = get_path_info(base_path=base_path, ignore=ignore) | ||
|
|
||
| empty_dirs = find_empty_directories(paths=paths) | ||
| similar_dirs = find_similar_directories(paths=paths) | ||
|
|
||
| report = { | ||
| "file_extensions": count_file_extensions(paths=paths), | ||
| "empty_directories": empty_dirs if empty_dirs else [None], | ||
| "similarly_named_directories": similar_dirs if similar_dirs else [None], | ||
| } | ||
|
|
||
| if print_report: | ||
| print("\nnViz Path Report:\n--------------------") | ||
| print("File Extensions:") | ||
| for ext, count in report["file_extensions"].items(): | ||
| print(f" {ext}: {count}") | ||
| print("\nEmpty Directories:") | ||
| for directory in report["empty_directories"]: | ||
| print(f" {directory}") | ||
| print("\nSimilarly Named Directories:") | ||
| if report["similarly_named_directories"] == [None]: | ||
| print(" None") | ||
| else: | ||
| for dir1, dir2 in report["similarly_named_directories"]: | ||
| print(f" {dir1} <-> {dir2}") | ||
|
|
||
| return report |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| #! /bin/bash | ||
|
|
||
| # This script is used to test the configuration of the Linux system. | ||
| # We referenced the following to help with configuration: | ||
| # https://github.com/pyvista/setup-headless-display-action/blob/v3/action.yml | ||
|
|
||
| # One way to run this script through a non-Linux or "clean" environment | ||
| # is through Docker. For example: | ||
| # docker run --rm -it --platform linux/amd64 -w /app -v $PWD:/app python:3.11 /bin/bash | ||
|
|
||
| # install dependencies | ||
| apt update | ||
| apt-get install -y \ | ||
| libglx-mesa0 \ | ||
| libgl1 \ | ||
| xvfb \ | ||
| x11-xserver-utils \ | ||
| herbstluftwm \ | ||
| libdbus-1-3 \ | ||
| libegl1 \ | ||
| libopengl0 \ | ||
| libosmesa6 \ | ||
| libxcb-cursor0 \ | ||
| libxcb-icccm4 \ | ||
| libxcb-image0 \ | ||
| libxcb-keysyms1 \ | ||
| libxcb-randr0 \ | ||
| libxcb-render-util0 \ | ||
| libxcb-shape0 \ | ||
| libxcb-xfixes0 \ | ||
| libxcb-xinerama0 \ | ||
| libxcb-xinput0 \ | ||
| libxkbcommon-x11-0 \ | ||
| mesa-utils \ | ||
| x11-utils | ||
|
|
||
| # Set up the display | ||
| export DISPLAY=:99.0 | ||
| Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & | ||
| sleep 3 | ||
|
|
||
| # Start the window manager | ||
| herbstluftwm & | ||
| sleep 3 | ||
|
|
||
| # install uv | ||
| curl -LsSf https://astral.sh/uv/install.sh | sh | ||
| # shellcheck disable=SC1091 | ||
| source "$HOME/.local/bin/env" | ||
|
|
||
| # install pre-commit | ||
| pip install pre-commit | ||
|
|
||
| # install pre-commit hooks | ||
| pre-commit install-hooks |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.