Convert Blender-Purge to a more generic Blender-Crawl Tool #42

Merged
Nick Alberelli merged 34 commits from feature/blender-crawl into main 2023-05-17 15:38:47 +02:00
19 changed files with 338 additions and 968 deletions

View File

@ -0,0 +1,54 @@
# blender_crawl
`blender_crawl` is a command line tools to crawl directories for .blend files and execute a provied script.
## Table of Contents
- [Prerequisite](#prerequisite)
- [Installation](#installation)
- [How to get started](#how-to-get-started)
## Prerequisite
In order to use this tool you need:
- Python 3.5+
## Run without Installation
1. Clone this repository with `git clone https://projects.blender.org/studio/blender-studio-pipeline.git`
2. Run `cd blender-studio-pipeline/scripts/blender-crawl` to enter directory
3. Run program with `python blender_crawl /my-folder/`
## Installation
Download or clone this repository.
This repository is a command line tool that can be installed with the python packaging manager.
This script does the following (follow this if you want to do this manually or on another platform):
1. Clone this repository with `git clone https://projects.blender.org/studio/blender-studio-pipeline.git`
2. Run `cd blender-studio-pipeline/scripts/blender-crawl` to enter directory
3. Install with `python setup.py install`
4. Run with `blender_crawl /my-folder/`
5. Get help with `blender_crawl -h`
## How to get started
Run directly out of repo folder or follow above installation instructions. Give `blender_crawl` a path to a .blend file or a folder as first argument, The detected blend files will be opened in the background, the python script will be executed, and the file closes. If blender is not installed at the default location of your computer, you need to provide a blender executable using the --exec flag.
| Command | Description |
| ----------- | ----------- |
| --script| Path to blender python script(s) to execute inside .blend files during crawl.|
| -r, --recursive| If provided in combination with a folder path will perform recursive crawl|
| -f --filter| Provide a string to filter the found .blend files|
| -a, --ask| If provided there will be no confirmation prompt before running script on .blend files.|
| -p, --purge| Run 'built-in function to purge data-blocks from all .blend files found in crawl.'.|
| --exec| If provided user must provide blender executable path, OS default blender will not be used if found.|
| -h, --help| show the above help message and exit|
## Usage Examples
| Action | Command |
| ----------- | ----------- |
|Prints the names of .blends in Current Directory | `blender_crawl ./` |
|Print the names of .blends Recursively | `blender_crawl /my-folder/ --recursive` |
|Print only the names of .blends matching a provided string |`blender_crawl /my-folder/ --find string`|
|Run default 'Purge' script on .blends in Current Directory |`blender_crawl /my-folder/ --purge`|
|Run custom script on all .blends in Current Directory |`blender_crawl /my-folder/ --script /my-directory/my-script.py`|
|Ask/Prompt before script execution|`blender_crawl /my-folder/ --script /my-directory/my-script.py --ask`|
|Run with a custom blender executable|`blender_crawl /my-folder/ --exec /path-to-blender-executable/blender`|

View File

@ -0,0 +1,247 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
import argparse
import sys
import os
import subprocess
import argparse
import re
from pathlib import Path
from typing import List
# Command line arguments.
parser = argparse.ArgumentParser()
parser.add_argument(
"path",
help="Path to a file(s) or folder(s) on which to perform crawl",
nargs='+'
)
parser.add_argument(
"-e",
"--exec",
help="If --exec user must provide blender executable path, OS default blender will not be used if found.",
type=str,
)
parser.add_argument(
"-s",
"--script",
help="Path to blender python script(s) to execute inside .blend files during crawl. Execution is skipped if no script is provided",
nargs='+',
)
parser.add_argument(
"-r",
"--recursive",
help="If -r is provided in combination with a folder path will perform recursive crawl",
action="store_true",
)
parser.add_argument(
"-f",
"--filter",
help="Provide a string to filter the found .blend files",
)
parser.add_argument(
"-a",
"--ask",
help="If --ask is provided there will be no confirmation prompt before running script on .blend files.",
action="store_true",
)
parser.add_argument(
"-p",
"--purge",
help="Run 'built-in function to purge data-blocks from all .blend files found in crawl.'.",
action="store_true",
)
def cancel_program(message: str):
print(message)
sys.exit(0)
def find_executable() -> Path:
if os.name != 'nt':
output = subprocess.run(
['which', 'blender'], capture_output=True, encoding="utf-8"
)
if output.returncode == 0:
# Returncode includes \n in string to indicate a new line
return Path(output.stdout.strip('\n'))
cancel_program("Blender Executable not found please provide --exec argument")
def prompt_confirm(list_length: int):
file_plural = "files" if list_length > 1 else "file"
confirm_str = f"Do you want to crawl {list_length} {file_plural}? ([y]es/[n]o)"
while True:
user_input = input(confirm_str).lower()
if not user_input in ["yes", "no", "y", "n"]:
print("\nPlease enter a valid answer!")
continue
if user_input in ["no", "n"]:
print("\nProcess was canceled.")
return False
else:
return True
def is_filepath_blend(path: Path) -> None:
# Check if path is file.
if not path.is_file():
cancel_program(f"Not a file: {path.suffix}")
# Check if path is blend file.
if path.suffix != ".blend":
cancel_program(f"Not a blend file: {path.suffix}")
def check_file_exists(file_path_str: str, error_msg: str):
if file_path_str is None:
return
file_path = Path(file_path_str).absolute()
if file_path.exists():
return file_path
else:
cancel_program(error_msg)
def get_purge_path(purge: bool):
# Cancel function if user has not supplied purge arg
if not purge:
return
scripts_directory = Path((os.path.dirname(__file__))).joinpath("default_scripts/")
purge_script = os.path.join(scripts_directory.resolve(), "purge.py")
return check_file_exists(str(purge_script), "Default scripts location may be invalid")
def main() -> int:
import sys
"""Crawl blender files in a directory and run a provided scripts"""
# Parse arguments.
args = parser.parse_args()
purge_path = get_purge_path(args.purge)
recursive = args.recursive
exec = args.exec
regex = args.filter
script_input = args.script
ask_for_confirmation = args.ask
scripts = []
if script_input:
for script in script_input:
script_name = check_file_exists(
script,
"No --script was not provided as argument, printed found .blend files, exiting program.",
)
scripts.append(script_name)
# Purge is optional so it can be none
if purge_path is not None:
scripts.append(purge_path)
if not exec:
blender_exec = find_executable()
else:
blender_exec = Path(exec).absolute()
if not blender_exec.exists():
cancel_program("Blender Executable Path is not valid")
# Vars.
files = []
for item in args.path:
file_path = Path(item).absolute()
if not file_path.exists():
cancel_program(f"Path does not exist: {file_path.as_posix()}")
# Collect files to crawl
# if dir.
if file_path.is_dir():
if recursive:
blend_files = [
f for f in file_path.glob("**/*") if f.is_file() and f.suffix == ".blend"
]
else:
blend_files = [
f for f in file_path.iterdir() if f.is_file() and f.suffix == ".blend"
]
files.extend(blend_files)
# If just one file.
else:
is_filepath_blend(file_path)
files.append(file_path)
# Apply regex.
if regex:
to_remove: List[Path] = []
for p in files:
match = re.search(regex, p.as_posix())
if not match:
to_remove.append(p)
for p in to_remove:
files.remove(p)
# Can only happen on folder here.
if not files:
print(" Found no .blend files to crawl")
sys.exit(0)
# Sort.
files.sort(key=lambda f: f.name)
for file in files:
print(f"Found: `{file}`")
if ask_for_confirmation:
if not prompt_confirm(len(files)):
sys.exit(0)
if not scripts:
cancel_program(
"No script files were provided to execute."
)
sys.exit(0)
# crawl each file two times.
for blend_file in files:
for script in scripts:
cmd_list = (
blender_exec.as_posix(),
blend_file.as_posix(),
"-b",
"-P",
script,
"--factory-startup",
)
process = subprocess.Popen(cmd_list, shell=False)
if process.wait() != 0:
cancel_program(f"Blender Crashed on file: {blend_file.as_posix()}")
return 0
if __name__ == "__main__":
main()

View File

@ -19,25 +19,19 @@
#
# (c) 2021, Blender Foundation
import sys
import logging
logger = logging.getLogger(__name__)
import bpy
# Setup prefs.
bpy.context.preferences.filepaths.save_version = 0
bpy.context.preferences.filepaths.save_version = 0 #TODO Figure out why this is here
# Purge.
logger.info("Starting Recursive Purge")
print("Starting Recursive Purge")
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
# Save.
bpy.ops.wm.save_mainfile()
logger.info("Saved file: %s", bpy.data.filepath)
print("Saved file: %s", bpy.data.filepath)
# Quit.
logger.info("Closing File")
print("Closing File")
bpy.ops.wm.quit_blender()

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
"""The setup script for blender-crawl."""
from setuptools import setup
with open("README.md") as readme_file:
readme = readme_file.read()
setup(
author="Nick Alberelli",
author_email="nick@blender.org",
python_requires=">=3.5",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
],
description="Command line tool to perform recursive crawl blend files from the console",
long_description=readme,
include_package_data=True,
keywords="blender_crawl",
name="blender_crawl",
packages=["blender_crawl", "blender_crawl.default_scripts",],
version="0.1.0",
entry_points={"console_scripts": ["blender_crawl = blender_crawl.__main__:main"]},
package_data={'blender_crawl.default_scripts': ['*']}, #TODO Verify this is working correctly after install
)

View File

@ -1,106 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
.venv*
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# IDE settings
.vscode/

View File

@ -1,71 +0,0 @@
# blender-purge
blender-purge is a command line tools to purge orphan data of blend files via the console.
## Table of Contents
- [Prerequisite](#prerequisite)
- [Installation](#installation)
- [How to get started](#how-to-get-started)
- [Development](#development)
## Prerequisite
In order to use this tool you need:
- python3
- pip
- svn
## Installation
Download or clone this repository.
This repository is a command line tool that can be installed with the python packaging manager.
In the root of this project you can find a `install.sh` script which simplifies this process on linux.
This script does the following (follow this if you want to do this manually or on another platform):
1. Install pip
2. Open a terminal in the root of the project directory
3. Run `python3 setup.py bdist_wheel` which builds a wheel in ./dist
4. Run `pip3 install dist/<name_of_wheel> --user`
Ensure your $PATH variable contains:
Linux/MacOs:
- `$HOME/.local/lib/python3.8/site-packages`
- `$HOME/.local/bin`
Windows:
- TODO
Open a new console and write `bpurge` to verify successful install.
## How to get started
After install you can write `bpurge` in to the console.
If you use the tool the first time it will ask you to specify a path to a blender executable and a path to the svn project directory, which will be saved in a configuration file:
Linux/MacOs:
- `$home/.config/blender-purge/config.json`
Windows:
- `$home/blender-purge/config.json`
Give `bpurge` a path to a .blend file or a folder as first argument.
The detected blend files will be opened in the background, their orphan data will be
purged recursively, the file gets saved and closed again. This will happen twice for each .blend file.
You can modify the tool by providing these command line arguments:
- first arguments needs to be a path to a .blend file or a folder
- **-R / --recursive**: If -R is provided in combination with a folder path will perform recursive purge.
- **-N / --nocommit**: If -N is provided there will be no svn commit prompt with the purged files.
- **--regex**: Provide any regex pattern that will be performed on each found filepath with re.search().
- **--yes**: If --yes is provided there will be no confirmation prompt.
## Development
In the project root you will find a `pyproject.toml` and `peotry.lock` file.
With `poetry` you can easily generate a virtual env for the project which should get you setup quickly.
Basic Usage: https://python-poetry.org/docs/basic-usage/

View File

@ -1,25 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
"""Top-level package for blender_purge."""
__author__ = """Paul Golter"""
__email__ = "paul@blender.org"
__version__ = "0.1.0"

View File

@ -1,328 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
import sys
import subprocess
import argparse
import json
import re
from pathlib import Path
from typing import Tuple, List, Dict, Any, Union, Optional
from blender_purge import vars
from blender_purge.svn import SvnRepo
from blender_purge.log import LoggerFactory
from blender_purge.exception import SomethingWentWrongException, WrongInputException
logger = LoggerFactory.getLogger()
def exception_handler(func):
def func_wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except WrongInputException as error:
logger.info(
"# Oops. Seems like you gave some wrong input!"
f"\n# Error: {error}"
"\n# Program will be cancelled."
)
cancel_program()
except SomethingWentWrongException as error:
logger.info(
"# Oops. Something went wrong during the execution of the Program!"
f"\n# Error: {error}"
"\n# Program will be cancelled."
)
cancel_program()
return func_wrapper
def cancel_program() -> None:
logger.info("# Exiting blender-purge")
sys.exit(0)
def get_blender_path() -> Path:
config_path = get_config_path()
json_obj = load_json(config_path)
return Path(json_obj["blender_path"])
def get_project_root_path() -> Path:
config_path = get_config_path()
json_obj = load_json(config_path)
return Path(json_obj["project_root"])
def get_cmd_list(path: Path) -> Tuple[str]:
cmd_list: Tuple[str] = (
get_blender_path().as_posix(),
path.as_posix(),
"-b",
"-P",
f"{vars.PURGE_PATH}",
"--factory-startup",
)
return cmd_list
def validate_user_input(user_input, options):
if user_input.lower() in options:
return True
else:
return False
def prompt_confirm(path_list: List[Path]):
options = ["yes", "no", "y", "n"]
list_str = "\n".join([p.as_posix() for p in path_list])
noun = "files" if len(path_list) > 1 else "file"
confirm_str = f"# Do you want to purge {len(path_list)} {noun}? ([y]es/[n]o)"
input_str = "# Files to purge:" + "\n" + list_str + "\n\n" + confirm_str
while True:
user_input = input(input_str)
if validate_user_input(user_input, options):
if user_input in ["no", "n"]:
logger.info("\n# Process was canceled.")
return False
else:
return True
logger.info("\n# Please enter a valid answer!")
continue
def run_check():
cmd_list: Tuple[str] = (
get_blender_path().as_posix(),
"-b",
"-P",
f"{vars.CHECK_PATH}",
)
p = subprocess.Popen(cmd_list)
return p.wait()
def purge_file(path: Path) -> int:
# Get cmd list.
cmd_list = get_cmd_list(path)
p = subprocess.Popen(cmd_list, shell=False)
# Stdout, stderr = p.communicate().
return p.wait()
def is_filepath_valid(path: Path) -> None:
# Check if path is file.
if not path.is_file():
raise WrongInputException(f"Not a file: {path.suffix}")
# Check if path is blend file.
if path.suffix != ".blend":
raise WrongInputException(f"Not a blend file: {path.suffix}")
def get_config_path() -> Path:
home = Path.home()
if sys.platform == "win32":
return home / "blender-purge/config.json"
elif sys.platform == "linux":
return home / ".config/blender-purge/config.json"
elif sys.platform == "darwin":
return home / ".config/blender-purge/config.json"
def create_config_file(config_path: Path) -> None:
if config_path.exists():
return
try:
with open(config_path.as_posix(), "w") as file:
json.dump({}, file)
except:
raise SomethingWentWrongException(
f"# Something went wrong creating config file at: {config_path.as_posix()}"
)
logger.info(f"# Created config file at: {config_path.as_posix()}")
def load_json(path: Path) -> Any:
with open(path.as_posix(), "r") as file:
obj = json.load(file)
return obj
def save_to_json(obj: Any, path: Path) -> None:
with open(path.as_posix(), "w") as file:
json.dump(obj, file, indent=4)
def input_path(question: str) -> Path:
while True:
user_input = input(question)
try:
path = Path(user_input)
except:
logger.error("# Invalid input")
continue
if path.exists():
return path.absolute()
else:
logger.info("# Path does not exist")
def input_filepath(question: str) -> Path:
while True:
path = input_path(question)
if not path.is_file():
continue
return path
def setup_config() -> None:
config_path = get_config_path()
config_path.parent.mkdir(parents=True, exist_ok=True)
blender_path = input_filepath("# Path to Blender binary: ")
project_root = input_path("# Path to SVN project root: ")
obj = {
"blender_path": blender_path.as_posix(),
"project_root": project_root.as_posix(),
}
save_to_json(obj, config_path)
logger.info("Updatet config at: %s", config_path.as_posix())
def is_config_valid() -> bool:
keys = ["blender_path", "project_root"]
config_path = get_config_path()
json_obj = load_json(config_path)
for key in keys:
if key not in json_obj:
return False
if not json_obj[key]:
return False
return True
@exception_handler
def purge(args: argparse.Namespace) -> int:
# Parse arguments.
path = Path(args.path).absolute()
recursive = args.recursive
config_path = get_config_path()
no_commit = args.nocommit
regex = args.regex
yes = args.yes
# Check config file.
if not config_path.exists():
logger.info("# Seems like you are starting blender-purge for the first time!")
logger.info("# Some things needs to be configured")
setup_config()
else:
if not is_config_valid():
logger.info("# Config file at: %s is not valid", config_path.as_posix())
logger.info("# Please set it up again")
setup_config()
# Check user input.
if not path:
raise WrongInputException("Please provide a path as first argument")
if not path.exists():
raise WrongInputException(f"Path does not exist: {path.as_posix()}")
# Vars.
files = []
# Collect files to purge
# if dir.
if path.is_dir():
if recursive:
blend_files = [
f for f in path.glob("**/*") if f.is_file() and f.suffix == ".blend"
]
else:
blend_files = [
f for f in path.iterdir() if f.is_file() and f.suffix == ".blend"
]
files.extend(blend_files)
# If just one file.
else:
is_filepath_valid(path)
files.append(path)
# Apply regex.
if regex:
to_remove: List[Path] = []
for p in files:
match = re.search(regex, p.as_posix())
if not match:
to_remove.append(p)
for p in to_remove:
files.remove(p)
# Can only happen on folder here.
if not files:
logger.info("# Found no .blend files to purge")
cancel_program()
# Sort.
files.sort(key=lambda f: f.name)
# Prompt confirm.
if not yes:
if not prompt_confirm(files):
cancel_program()
"""
# Perform check of correct preference settings.
return_code = run_check()
if return_code == 1:
raise SomethingWentWrongException(
"Override auto resync is turned off. Turn it on in the preferences and try again."
)
"""
# Purge each file two times.
for blend_file in files:
for i in range(vars.PURGE_AMOUNT):
return_code = purge_file(blend_file)
if return_code != 0:
raise SomethingWentWrongException(
f"Blender Crashed on file: {blend_file.as_posix()}",
)
# Commit to svn.
if no_commit:
return 0
project_root = get_project_root_path()
svn_repo = SvnRepo(project_root)
file_rel = [p.relative_to(project_root) for p in files]
svn_repo.commit(file_rel)
return 0

View File

@ -1,32 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
import sys
import bpy
import logging
logger = logging.getLogger(__name__)
# Check if recursive is on.
if not bpy.context.preferences.experimental.override_auto_resync:
logger.error("Override auto resync is turned off!")
sys.exit(1)

View File

@ -1,72 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
import argparse
import sys
import os
import importlib
from pathlib import Path
from blender_purge import app
from blender_purge.log import LoggerFactory
importlib.reload(app)
logger = LoggerFactory.getLogger()
# Command line arguments.
parser = argparse.ArgumentParser()
parser.add_argument(
"path", help="Path to a file or folder on which to perform purge", type=str
)
parser.add_argument(
"-R",
"--recursive",
help="If -R is provided in combination with a folder path will perform recursive purge",
action="store_true",
)
parser.add_argument(
"-N",
"--nocommit",
help="If -N is provided there will be no svn commit prompt with the purged files.",
action="store_true",
)
parser.add_argument(
"--regex",
help="Provide any regex pattern that will be performed on each found filepath with re.search()",
)
parser.add_argument(
"--yes",
help="If --yes is provided there will be no confirmation prompt.",
action="store_true",
)
def main():
args = parser.parse_args()
app.purge(args)
if __name__ == "__main__":
main()

View File

@ -1,27 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
class WrongInputException(Exception):
pass
class SomethingWentWrongException(Exception):
pass

View File

@ -1,42 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
import sys
import logging
class LoggerFactory:
"""
Utility class to streamline logger creation
"""
formatter = logging.Formatter("%(name)s: %(message)s")
consoleHandler = logging.StreamHandler(sys.stdout)
level = logging.INFO
@classmethod
def getLogger(cls, name=__name__):
logger = logging.getLogger(name)
logger.addHandler(cls.consoleHandler)
logger.setLevel(cls.level)
return logger

View File

@ -1,118 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
import subprocess
import os
from typing import List, Union, Tuple, Any, Dict, Optional
from pathlib import Path
class SvnRepo:
def __init__(self, path: Path) -> None:
self._orig_pwd = Path(os.path.abspath(os.getcwd()))
self._path = path.absolute()
@property
def path(self) -> Path:
return self._path
def status(self) -> List[str]:
output = str(
subprocess.check_output(
["svn status"], shell=True, cwd=self._path.as_posix()
),
"utf-8",
)
# Split output in string lines.
split = output.split("\n")
# Remove empty lines.
while True:
try:
split.remove("")
except ValueError:
break
return split
def get_modified(self, suffix: str = ".*") -> List[Path]:
output = self.status()
if not output:
return []
path_list: List[Path] = []
# Assemble path list.
for idx, line in enumerate(output):
if not line.startswith("M"):
continue
path = Path(line[5:].strip())
# If no suffix supplied append all files.
if suffix == ".*":
path_list.append(path)
# If suffix supplied only collect files that match.
else:
if path.suffix == suffix:
path_list.append(path)
return path_list
def revert(self, relpath_list: List[Path]) -> subprocess.Popen:
arg_list = " ".join([p.as_posix() for p in relpath_list])
process = subprocess.call(
(f"svn revert {arg_list}"), shell=True, cwd=self._path.as_posix()
)
return process
def revert_all(self) -> None:
modified = self.get_modified()
self.revert(modified)
def commit(self, relpath_list: List[Path]) -> Optional[subprocess.Popen]:
if not relpath_list:
return None
cmd_list = f'svn commit {" ".join([p.as_posix() for p in relpath_list])}'
process = subprocess.call(cmd_list, shell=True, cwd=self._path.as_posix())
return process
def get_untracked(self) -> List[Path]:
output = self.status()
if not output:
return []
path_list: List[Path] = []
# Assemble path list.
for idx, line in enumerate(output):
if not line.startswith("?"):
continue
path = Path(line[5:].strip())
path_list.append(path)
return path_list
if __name__ == "__main__":
# Test status.
repo = SvnRepo(Path("/media/data/sprites"))
modified = repo.get_modified()
print(modified)
repo.commit(modified[:2])

View File

@ -1,29 +0,0 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2021, Blender Foundation
import os
from pathlib import Path
PURGE_PATH = Path(os.path.abspath(__file__)).parent.joinpath("purge.py")
CHECK_PATH = Path(os.path.abspath(__file__)).parent.joinpath("check.py")
BLENDER_PATH = "/media/data/blender_guest/cmake_release/bin/blender"
PROJECT_PATH = "/media/data/sprites"
PURGE_AMOUNT = 2

View File

@ -1,46 +0,0 @@
#!/bin/bash
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color.
# Check if pip3 is installed.
if ! command -v pip3 &> /dev/null
then
echo "Pip3 is not installed"
# Ask user to install pip.
while true; do
read -p "Do you wish to install this program? (Yy/Nn)" yn
case $yn in
[Yy]* ) sudo apt install python3-pip; break;;
[Nn]* ) exit;;
* ) echo "Please answer yes or no.";;
esac
done
fi
# Cd into directory of install.sh script.
dirpath=`dirname "$0"`
cd $dirpath
# Build wheel.
python3 setup.py bdist_wheel
# Install wheel with pip.
pip3 install dist/blender_purge-0.1.0-py2.py3-none-any.whl --user --force-reinstall
# Check if PATH variable is correct.
if ! [[ ":$PATH:" == *":$HOME/.local/lib/python3.8/site-packages:"* ]]; then
printf "\n${RED}\$HOME/.local/lib/python3.8/site-packages is missing in PATH variable\n"
printf "Please add 'export PATH=\"\$PATH:$HOME/.local/lib/python3.8/site-packages\"' to the file: \$HOME/.profile${NC}\n"
fi
if ! [[ ":$PATH:" == *":$HOME/.local/bin:"* ]]; then
printf "\n${RED}\$HOME/.local/bin is missing in PATH variable\n"
printf "Please add 'export PATH=\"\$PATH:$HOME/.local/bin\"' to the file: \$HOME/.profile${NC}\n"
fi
# Log end.
printf "\n${GREEN}Installed blender-purge. Type 'bpurge' in console to start program!\n"
printf "To uninstall type 'pip3 uninstall blender-purge'${NC}\n"

View File

@ -1,16 +0,0 @@
[bumpversion]
current_version = 0.1.0
commit = True
tag = True
[bumpversion:file:setup.py]
search = version='{current_version}'
replace = version='{new_version}'
[bdist_wheel]
universal = 1
[flake8]
exclude = docs
[aliases]

View File

@ -1,46 +0,0 @@
#!/usr/bin/env python
"""The setup script."""
from setuptools import setup, find_packages
with open("README.md") as readme_file:
readme = readme_file.read()
with open("HISTORY.md") as history_file:
history = history_file.read()
requirements = []
setup_requirements = []
test_requirements = []
setup(
author="Paul Golter",
author_email="paul@blender.org",
python_requires=">=3.5",
classifiers=[
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
],
description="Command line tool to perform recursive purge of blend files in the console",
install_requires=requirements,
long_description=readme + "\n\n" + history,
include_package_data=True,
keywords="blender_purge",
name="blender_purge",
packages=find_packages(include=["blender_purge", "blender_purge.*"]),
setup_requires=setup_requirements,
test_suite="tests",
tests_require=test_requirements,
version="0.1.0",
zip_safe=False,
entry_points={"console_scripts": ["bpurge=blender_purge.cli:main"]},
)