Add 'bam copy' command.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
__version__ = "0.0.4.8"
|
||||
__version__ = "0.0.5.1"
|
||||
|
||||
def main(argv=sys.argv):
|
||||
from .cli import main
|
||||
|
114
bam/blend/blendfile_copy.py
Normal file
114
bam/blend/blendfile_copy.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ***** 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 *****
|
||||
|
||||
"""
|
||||
A simply utility to copy blend files and their deps to a new location.
|
||||
|
||||
Similar to packing, but don't attempt any path remapping.
|
||||
"""
|
||||
|
||||
from bam.blend import blendfile_path_walker
|
||||
|
||||
TIMEIT = False
|
||||
|
||||
# ------------------
|
||||
# Ensure module path
|
||||
import os
|
||||
import sys
|
||||
path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "modules"))
|
||||
if path not in sys.path:
|
||||
sys.path.append(path)
|
||||
del os, sys, path
|
||||
# --------
|
||||
|
||||
|
||||
def copy_paths(
|
||||
paths,
|
||||
output,
|
||||
base,
|
||||
|
||||
# load every libs dep, not just used deps.
|
||||
all_deps=False,
|
||||
# yield reports
|
||||
report=None,
|
||||
|
||||
# Filename filter, allow to exclude files from the pack,
|
||||
# function takes a string returns True if the files should be included.
|
||||
filename_filter=None,
|
||||
):
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from bam.utils.system import colorize, is_subdir
|
||||
|
||||
path_copy_files = set(paths)
|
||||
|
||||
# Avoid walking over same libs many times
|
||||
lib_visit = {}
|
||||
|
||||
yield report("Reading %d blend file(s)\n" % len(paths))
|
||||
for blendfile_src in paths:
|
||||
yield report(" %s: %r\n" % (colorize("blend", color='blue'), blendfile_src))
|
||||
for fp, (rootdir, fp_blend_basename) in blendfile_path_walker.FilePath.visit_from_blend(
|
||||
blendfile_src,
|
||||
readonly=True,
|
||||
recursive=True,
|
||||
recursive_all=all_deps,
|
||||
lib_visit=lib_visit,
|
||||
):
|
||||
|
||||
f_abs = os.path.normpath(fp.filepath_absolute)
|
||||
path_copy_files.add(f_abs)
|
||||
|
||||
# Source -> Dest Map
|
||||
path_src_dst_map = {}
|
||||
|
||||
for path_src in sorted(path_copy_files):
|
||||
|
||||
if filename_filter and not filename_filter(path_src):
|
||||
yield report(" %s: %r\n" % (colorize("exclude", color='yellow'), path_src))
|
||||
continue
|
||||
|
||||
if not os.path.exists(path_src):
|
||||
yield report(" %s: %r\n" % (colorize("missing path", color='red'), path_src))
|
||||
continue
|
||||
|
||||
if not is_subdir(path_src, base):
|
||||
yield report(" %s: %r\n" % (colorize("external path ignored", color='red'), path_src))
|
||||
continue
|
||||
|
||||
path_rel = os.path.relpath(path_src, base)
|
||||
path_dst = os.path.join(output, path_rel)
|
||||
|
||||
path_src_dst_map[path_src] = path_dst
|
||||
|
||||
# Create directories
|
||||
path_dst_dir = {os.path.dirname(path_dst) for path_dst in path_src_dst_map.values()}
|
||||
yield report("Creating %d directories in %r\n" % (len(path_dst_dir), output))
|
||||
for path_dir in sorted(path_dst_dir):
|
||||
os.makedirs(path_dir, exist_ok=True)
|
||||
del path_dst_dir
|
||||
|
||||
# Copy files
|
||||
yield report("Copying %d files to %r\n" % (len(path_src_dst_map), output))
|
||||
for path_src, path_dst in sorted(path_src_dst_map.items()):
|
||||
yield report(" %s: %r -> %r\n" % (colorize("copying", color='blue'), path_src, path_dst))
|
||||
shutil.copy(path_src, path_dst)
|
136
bam/cli.py
136
bam/cli.py
@@ -1386,6 +1386,63 @@ class bam_commands:
|
||||
):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def copy(
|
||||
paths,
|
||||
output,
|
||||
base,
|
||||
all_deps=False,
|
||||
use_quiet=False,
|
||||
filename_filter=None,
|
||||
):
|
||||
# Local packing (don't use any project/session stuff)
|
||||
from .blend import blendfile_copy
|
||||
from bam.utils.system import is_subdir
|
||||
|
||||
paths = [os.path.abspath(path) for path in paths]
|
||||
base = os.path.abspath(base)
|
||||
output = os.path.abspath(output)
|
||||
|
||||
# check all blends are in the base path
|
||||
for path in paths:
|
||||
if not is_subdir(path, base):
|
||||
fatal("Input blend file %r is not a sub directory of %r" % (path, base))
|
||||
|
||||
if use_quiet:
|
||||
report = lambda msg: None
|
||||
else:
|
||||
report = lambda msg: print(msg, end="")
|
||||
|
||||
# replace var with a pattern matching callback
|
||||
if filename_filter:
|
||||
# convert string into regex callback
|
||||
# "*.txt;*.png;*.rst" --> r".*\.txt$|.*\.png$|.*\.rst$"
|
||||
import re
|
||||
import fnmatch
|
||||
|
||||
compiled_pattern = re.compile(
|
||||
b'|'.join(fnmatch.translate(f).encode('utf-8')
|
||||
for f in filename_filter.split(";") if f),
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
def filename_filter(f):
|
||||
return (not filename_filter.compiled_pattern.match(f))
|
||||
filename_filter.compiled_pattern = compiled_pattern
|
||||
|
||||
del compiled_pattern
|
||||
del re, fnmatch
|
||||
|
||||
for msg in blendfile_copy.copy_paths(
|
||||
[path.encode('utf-8') for path in paths],
|
||||
output.encode('utf-8'),
|
||||
base.encode('utf-8'),
|
||||
all_deps=all_deps,
|
||||
report=report,
|
||||
filename_filter=filename_filter,
|
||||
):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def remap_start(
|
||||
paths,
|
||||
@@ -1466,6 +1523,7 @@ def init_argparse_common(
|
||||
use_all_deps=False,
|
||||
use_quiet=False,
|
||||
use_compress_level=False,
|
||||
use_exclude=False,
|
||||
):
|
||||
import argparse
|
||||
|
||||
@@ -1495,6 +1553,20 @@ def init_argparse_common(
|
||||
choices=('default', 'fast', 'best', 'store'),
|
||||
help="Compression level for resulting archive",
|
||||
)
|
||||
if use_exclude:
|
||||
subparse.add_argument(
|
||||
"-e", "--exclude", dest="exclude", metavar='PATTERN(S)', required=False,
|
||||
default="",
|
||||
help="""
|
||||
Optionally exclude files from the pack.
|
||||
|
||||
Using Unix shell-style wildcards *(case insensitive)*.
|
||||
``--exclude="*.png"``
|
||||
|
||||
Multiple patterns can be passed using the ``;`` separator.
|
||||
``--exclude="*.txt;*.avi;*.wav"``
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def create_argparse_init(subparsers):
|
||||
@@ -1707,21 +1779,8 @@ def create_argparse_pack(subparsers):
|
||||
choices=('ZIP', 'FILE'),
|
||||
help="Output file or a directory when multiple inputs are passed",
|
||||
)
|
||||
subparse.add_argument(
|
||||
"-e", "--exclude", dest="exclude", metavar='PATTERN(S)', required=False,
|
||||
default="",
|
||||
help="""
|
||||
Optionally exclude files from the pack.
|
||||
|
||||
Using Unix shell-style wildcards *(case insensitive)*.
|
||||
``--exclude="*.png"``
|
||||
|
||||
Multiple patterns can be passed using the ``;`` separator.
|
||||
``--exclude="*.txt;*.avi;*.wav"``
|
||||
"""
|
||||
)
|
||||
|
||||
init_argparse_common(subparse, use_all_deps=True, use_quiet=True, use_compress_level=True)
|
||||
init_argparse_common(subparse, use_all_deps=True, use_quiet=True, use_compress_level=True, use_exclude=True)
|
||||
|
||||
subparse.set_defaults(
|
||||
func=lambda args:
|
||||
@@ -1739,6 +1798,54 @@ def create_argparse_pack(subparsers):
|
||||
)
|
||||
|
||||
|
||||
def create_argparse_copy(subparsers):
|
||||
import argparse
|
||||
subparse = subparsers.add_parser(
|
||||
"copy", aliases=("cp",),
|
||||
help="Copy blend file(s) and their dependencies to a new location (maintaining the directory structure).",
|
||||
description=
|
||||
"""
|
||||
The line below will copy ``scene.blend`` to ``/destination/to/scene.blend``.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
bam copy /path/to/scene.blend --base=/path --output=/destination
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# you can also copy multiple files
|
||||
bam copy /path/to/scene.blend /path/other/file.blend --base=/path --output /other/destination
|
||||
""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
subparse.add_argument(
|
||||
dest="paths", nargs="+",
|
||||
help="Path(s) to blend files to operate on",
|
||||
)
|
||||
subparse.add_argument(
|
||||
"-o", "--output", dest="output", metavar='DIR', required=True,
|
||||
help="Output directory where where files will be copied to",
|
||||
)
|
||||
subparse.add_argument(
|
||||
"-b", "--base", dest="base", metavar='DIR', required=True,
|
||||
help="Base directory for input paths (files outside this path will be omitted)",
|
||||
)
|
||||
|
||||
init_argparse_common(subparse, use_all_deps=True, use_quiet=True, use_exclude=True)
|
||||
|
||||
subparse.set_defaults(
|
||||
func=lambda args:
|
||||
bam_commands.copy(
|
||||
args.paths,
|
||||
args.output,
|
||||
args.base,
|
||||
all_deps=args.all_deps,
|
||||
use_quiet=args.use_quiet,
|
||||
filename_filter=args.exclude,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def create_argparse_remap(subparsers):
|
||||
import argparse
|
||||
|
||||
@@ -1874,6 +1981,7 @@ def create_argparse():
|
||||
# non-bam project commands
|
||||
create_argparse_deps(subparsers)
|
||||
create_argparse_pack(subparsers)
|
||||
create_argparse_copy(subparsers)
|
||||
create_argparse_remap(subparsers)
|
||||
|
||||
return parser
|
||||
|
@@ -123,3 +123,27 @@ def is_compressed_filetype(filepath):
|
||||
# '.gz', '.tgz',
|
||||
# '.zip',
|
||||
}
|
||||
|
||||
|
||||
def is_subdir(path, directory):
|
||||
"""
|
||||
Returns true if *path* in a subdirectory of *directory*.
|
||||
"""
|
||||
import os
|
||||
from os.path import normpath, normcase
|
||||
path = normpath(normcase(path))
|
||||
directory = normpath(normcase(directory))
|
||||
|
||||
if isinstance(directory, bytes):
|
||||
sep_i = ord(os.sep)
|
||||
sep = os.sep.encode('ascii')
|
||||
else:
|
||||
sep_i = os.sep
|
||||
sep = os.sep
|
||||
|
||||
directory = directory.rstrip(sep)
|
||||
if len(path) > len(directory):
|
||||
if path.startswith(directory):
|
||||
return (path[len(directory)] == sep_i)
|
||||
return False
|
||||
|
||||
|
Reference in New Issue
Block a user