From 9d47edb399f36381016eaa2514cb3f12fbafb0ab Mon Sep 17 00:00:00 2001 From: George Kurelic Date: Fri, 18 Nov 2022 12:21:26 -0600 Subject: [PATCH] Change AssetPaths args (#2575) * change asset path args * Add more options for asset path args. This way users aren't forced to use regexes, but can opt in if they want to. Plus we don't break backwards compatibility. * Remove backwards compatibility case. Co-authored-by: George FunBook * Extract local function. Co-authored-by: George FunBook * add static * update unit tests * prevent errors on duplicate names, throw warnings * spacing * use single array for better unique detection Co-authored-by: Joseph Cloutier --- flixel/system/FlxAssets.hx | 41 +++++-- flixel/system/macros/FlxAssetPaths.hx | 109 ++++++++++++------ .../flixel/system/macros/FlxAssetPathsTest.hx | 2 +- 3 files changed, 110 insertions(+), 42 deletions(-) diff --git a/flixel/system/FlxAssets.hx b/flixel/system/FlxAssets.hx index f8fd37e4c6..6219f1ed69 100644 --- a/flixel/system/FlxAssets.hx +++ b/flixel/system/FlxAssets.hx @@ -1,5 +1,6 @@ package flixel.system; +import haxe.macro.Expr; #if !macro import flash.display.BitmapData; import flash.display.Graphics; @@ -52,7 +53,7 @@ class FlxAssets * Example usage: * * ```haxe - * @:build(flixel.system.FlxAssets.buildFileReferences("assets/images")) + * @:build(flixel.system.FlxAssets.buildFileReferences("assets/images/")) * class Images {} * ``` * @@ -60,19 +61,45 @@ class FlxAssets * @author Mark Knol * @see http://blog.stroep.nl/2014/01/haxe-macros/ * - * @param directory The directory to scan for files - * @param subDirectories Whether to include subdirectories - * @param filterExtensions Example: `["jpg", "png", "gif"]` will only add files with that extension. + * @param directory The directory to scan for files + * @param subDirectories Whether to include subdirectories + * @param include A string or `EReg` of files to include. + * Example: `"*.jpg|*.png|*.gif"` will only add files with that extension + * @param exclude A string or `EReg` of files to exclude. + * Example: `"*exclude/*|*.ogg"` will exclude .ogg files and everything in the exclude folder + * @param rename A function that takes the file path and returns a valid haxe field name. */ - public static function buildFileReferences(directory:String = "assets/", subDirectories:Bool = false, - ?filterExtensions:Array, ?rename:String->String):Array + public static function buildFileReferences(directory = "assets/", subDirectories = false, + ?include:Expr, ?exclude:Expr, ?rename:String->Null):Array { #if doc_gen return []; #else - return flixel.system.macros.FlxAssetPaths.buildFileReferences(directory, subDirectories, filterExtensions, rename); + return flixel.system.macros.FlxAssetPaths.buildFileReferences(directory, subDirectories, + exprToRegex(include), exprToRegex(exclude), rename); #end } + + #if !doc_gen + private static function exprToRegex(expr:Expr):EReg + { + switch(expr.expr) + { + case null | EConst(CIdent("null")): + return null; + case EConst(CString(s, _)): + s = EReg.escape(s); + s = StringTools.replace(s, "\\*", ".*"); + s = StringTools.replace(s, "\\|", "|"); + return new EReg("^" + s + "$", ""); + case EConst(CRegexp(r, opt)): + return new EReg(r, opt); + default: + haxe.macro.Context.error("Value must be a string or regular expression.", expr.pos); + return null; + } + } + #end #end #if (!macro || doc_gen) diff --git a/flixel/system/macros/FlxAssetPaths.hx b/flixel/system/macros/FlxAssetPaths.hx index 7a410da1ff..8719afa98a 100644 --- a/flixel/system/macros/FlxAssetPaths.hx +++ b/flixel/system/macros/FlxAssetPaths.hx @@ -4,88 +4,118 @@ import haxe.macro.Context; import haxe.macro.Expr; import sys.FileSystem; -using flixel.util.FlxArrayUtil; using StringTools; +using flixel.util.FlxArrayUtil; class FlxAssetPaths { - public static function buildFileReferences(directory:String = "assets/", subDirectories:Bool = false, ?filterExtensions:Array, - ?rename:String->String):Array + public static function buildFileReferences(directory = "assets/", subDirectories = false, ?include:EReg, ?exclude:EReg, + ?rename:String->Null):Array { if (!directory.endsWith("/")) directory += "/"; Context.registerModuleDependency(Context.getLocalModule(), directory); - var fileReferences:Array = getFileReferences(directory, subDirectories, filterExtensions, rename); - var fields:Array = Context.getBuildFields(); + var fileReferences = addFileReferences([], directory, subDirectories, include, exclude, rename); + var fields = Context.getBuildFields(); + // create new fields based on file references! for (fileRef in fileReferences) - { - // create new field based on file references! - fields.push({ - name: fileRef.name, - doc: fileRef.documentation, - access: [Access.APublic, Access.AStatic, Access.AInline], - kind: FieldType.FVar(macro:String, macro $v{fileRef.value}), - pos: Context.currentPos() - }); - } + fields.push(fileRef.createField()); + return fields; } - static function getFileReferences(directory:String, subDirectories:Bool = false, ?filterExtensions:Array, - ?rename:String->String):Array + static function addFileReferences(fileReferences:Array, directory:String, subDirectories = false, ?include:EReg, ?exclude:EReg, + ?rename:String->Null):Array { - var fileReferences:Array = []; var resolvedPath = #if (ios || tvos) "../assets/" + directory #else directory #end; var directoryInfo = FileSystem.readDirectory(resolvedPath); for (name in directoryInfo) { - if (!FileSystem.isDirectory(resolvedPath + name)) + var path = resolvedPath + name; + + if (include != null && !include.match(path)) + continue; + + if (exclude != null && exclude.match(path)) + continue; + + if (!FileSystem.isDirectory(path)) { // ignore invisible files if (name.startsWith(".")) continue; - if (filterExtensions != null) - { - var extension:String = name.split(".").last(); // get the last string with a dot before it - if (!filterExtensions.contains(extension)) - continue; - } - - var reference = FileReference.fromPath(directory + name, rename); + var reference = FileReference.fromPath(path, rename); if (reference != null) - fileReferences.push(reference); + addIfUnique(fileReferences, reference); } else if (subDirectories) { - fileReferences = fileReferences.concat(getFileReferences(directory + name + "/", true, filterExtensions, rename)); + addFileReferences(fileReferences, directory + name + "/", true, include, exclude, rename); } } return fileReferences; } + + static function addIfUnique(fileReferences:Array, file:FileReference) + { + for (i in 0...fileReferences.length) + { + if (fileReferences[i].name == file.name) + { + var oldValue = fileReferences[i].value; + // if the old file is nested deeper in the folder structure + if (oldValue.split("/").length > file.value.split("/").length) + { + // replace it with the new one + fileReferences[i] = file; + Context.warning('Duplicate files named "${file.name}" ignoring $oldValue', Context.currentPos()); + } + else + { + Context.warning('Duplicate files named "${file.name}" ignoring ${file.value}', Context.currentPos()); + } + return; + } + } + + fileReferences.push(file); + } } private class FileReference { - private static var validIdentifierPattern = ~/^[_A-Za-z]\w*$/; + static var valid = ~/^[_A-Za-z]\w*$/; - public static function fromPath(value:String, ?rename:String->String):Null + public static function fromPath(value:String, ?library:String, ?rename:String->Null):Null { - // replace some forbidden names to underscores, since variables cannot have these symbols. - var name = value.split("/").last(); + var name = value; if (rename != null) { name = rename(name); + // exclude null + if (name == null) + return null; } + else + name = value.split("/").pop(); + // replace some forbidden names to underscores, since variables cannot have these symbols. name = name.split("-").join("_").split(".").join("__"); - if (!validIdentifierPattern.match(name)) // #1796 + if (!valid.match(name)) // #1796 + { + Context.warning('Invalid name: $name for file: $value', Context.currentPos()); return null; + } + + if (library != "default" && library != "" && library != null) + value = '$library:$value'; + return new FileReference(name, value); } @@ -99,4 +129,15 @@ private class FileReference this.value = value; this.documentation = "`\"" + value + "\"` (auto generated)."; } + + public function createField():Field + { + return { + name: name, + doc: documentation, + access: [Access.APublic, Access.AStatic, Access.AInline], + kind: FieldType.FVar(macro:String, macro $v{value}), + pos: Context.currentPos() + }; + } } diff --git a/tests/unit/src/flixel/system/macros/FlxAssetPathsTest.hx b/tests/unit/src/flixel/system/macros/FlxAssetPathsTest.hx index b5fce3ba6a..366252beaa 100644 --- a/tests/unit/src/flixel/system/macros/FlxAssetPathsTest.hx +++ b/tests/unit/src/flixel/system/macros/FlxAssetPathsTest.hx @@ -22,5 +22,5 @@ class FlxAssetPathsTest extends FlxTest @:build(flixel.system.FlxAssets.buildFileReferences("assets/FlxAssetPaths/invisibleFile")) class InvisibleFile {} -@:build(flixel.system.FlxAssets.buildFileReferences("assets/FlxAssetPaths/fileWithMultipleDots", false, ["txt"])) +@:build(flixel.system.FlxAssets.buildFileReferences("assets/FlxAssetPaths/fileWithMultipleDots", false, "*.txt")) class ExtensionFilterWithMultiDotFile {}