Last active
February 28, 2024 05:09
-
-
Save dyerw/6af307d4703361daed24 to your computer and use it in GitHub Desktop.
AssetCleaner - Removes all unused assets from an XCode project
This file contains 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
USAGE = \ | |
""" | |
Searches an XCode project to determine which image | |
assets aren't being used then removes them. | |
WARNING: This will delete things permanently if your | |
project is not under source control. | |
""" | |
import os | |
import sys | |
import shutil | |
import argparse | |
# Handle command line stuff | |
parser = argparse.ArgumentParser(description='Remove unused assets from XCode.', epilog=USAGE) | |
parser.add_argument('dir', help='location of XCode project') | |
parser.add_argument('-d', '--dry', help='don\'t remove assets, just display what would be removed', action='store_true') | |
parser.add_argument('-f', '--filter', help='only remove assets beginning with a string, non case sensitive', nargs=1) | |
args = parser.parse_args() | |
dir_to_clean = args.dir | |
dry_run = args.dry | |
prefix_filter = args.filter[0] if args.filter else None | |
# Extensions in which we're looking for used assets | |
EXTENSIONS = ['.h', '.m', '.xib', '.plist', '.storyboard', '.swift', '.html'] | |
def clean_png_filename(filename): | |
""" | |
Takes a png filename and strips the string to how it would be | |
found in the code. | |
e.g. "[email protected]" -> "text_field_grey" | |
""" | |
return filename.split("@")[0].split(".")[0] | |
if dry_run: | |
print "~~ Dry Run, nothing will be removed ~~" | |
# Gets a list of all files with any of the given extensions | |
# also gets a list of all image files | |
files_to_search = [] | |
images = [] | |
image_locations = {} # We use this to figure out what directories to delete later | |
for root, dirs, files in os.walk(dir_to_clean): | |
# AppIcon isn't directly referenced in the source, but we don't want to delete it | |
if "AppIcon" in root: | |
continue | |
files_to_search += [root + "/" + valid_file | |
for valid_file in files | |
if any([valid_file.lower().endswith(extension) | |
for extension in EXTENSIONS])] | |
new_images = set([clean_png_filename(image_file) | |
for image_file in files | |
if image_file.lower().endswith('.png')]) | |
for image in new_images: | |
image_locations[image] = root | |
images += new_images | |
files_left = len(files_to_search) | |
sys.stdout.write("%d files to search | %d images remaining" % (files_left, len(images))) | |
# Search each file for each string | |
for search_file in files_to_search: | |
with open(search_file, 'r') as f: | |
file_text = f.read() | |
for image in images: | |
if image in file_text: | |
images = filter(lambda imagename: imagename != image, images) | |
files_left -= 1 | |
sys.stdout.write("\r%d files to search | %d images remaining" % (files_left, len(images))) | |
sys.stdout.write("\n") | |
print "Found %d unused images" % len(images) | |
# Filter images based on supplied prefix | |
if prefix_filter: | |
images = filter(lambda imagename: imagename.lower().startswith(prefix_filter.lower()), | |
images) | |
# We're getting the folder locations of each image | |
# but only if that path contains ".xcassets" | |
# and if all the '.png' files in that folder are | |
# in the list of images to be deleted i.e. no images | |
# that aren't in the list are in the directory | |
assets_to_remove = [image_locations[image] | |
for image in images | |
if ".xcassets" in image_locations[image] \ | |
and set(map(clean_png_filename, | |
filter(lambda filename: filename.lower().endswith('.png'), | |
os.listdir(image_locations[image])))) | |
.issubset(images)] | |
print "Deleting %d assets" % len(assets_to_remove) | |
print "Removing: \n%s\n" % "\n".join(assets_to_remove) | |
if not dry_run: | |
for asset in assets_to_remove: | |
if os.path.exists(asset): | |
shutil.rmtree(asset) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi author
Can you share how to run this script?