Skip to content

Commit

Permalink
Change AssetPaths args (#2575)
Browse files Browse the repository at this point in the history
* 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 <gkurelic@gmail.com>

* Extract local function.

Co-authored-by: George FunBook <gkurelic@gmail.com>

* 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 <jkcloutier@gmail.com>
  • Loading branch information
Geokureli and player-03 authored Nov 18, 2022
1 parent f0087d6 commit 9d47edb
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 42 deletions.
41 changes: 34 additions & 7 deletions flixel/system/FlxAssets.hx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package flixel.system;

import haxe.macro.Expr;
#if !macro
import flash.display.BitmapData;
import flash.display.Graphics;
Expand Down Expand Up @@ -52,27 +53,53 @@ class FlxAssets
* Example usage:
*
* ```haxe
* @:build(flixel.system.FlxAssets.buildFileReferences("assets/images"))
* @:build(flixel.system.FlxAssets.buildFileReferences("assets/images/"))
* class Images {}
* ```
*
* Mostly copied from:
* @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<String>, ?rename:String->String):Array<haxe.macro.Expr.Field>
public static function buildFileReferences(directory = "assets/", subDirectories = false,
?include:Expr, ?exclude:Expr, ?rename:String->Null<String>):Array<Field>
{
#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)
Expand Down
109 changes: 75 additions & 34 deletions flixel/system/macros/FlxAssetPaths.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
?rename:String->String):Array<Field>
public static function buildFileReferences(directory = "assets/", subDirectories = false, ?include:EReg, ?exclude:EReg,
?rename:String->Null<String>):Array<Field>
{
if (!directory.endsWith("/"))
directory += "/";

Context.registerModuleDependency(Context.getLocalModule(), directory);

var fileReferences:Array<FileReference> = getFileReferences(directory, subDirectories, filterExtensions, rename);
var fields:Array<Field> = 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<String>,
?rename:String->String):Array<FileReference>
static function addFileReferences(fileReferences:Array<FileReference>, directory:String, subDirectories = false, ?include:EReg, ?exclude:EReg,
?rename:String->Null<String>):Array<FileReference>
{
var fileReferences:Array<FileReference> = [];
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<FileReference>, 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<FileReference>
public static function fromPath(value:String, ?library:String, ?rename:String->Null<String>):Null<FileReference>
{
// 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);
}

Expand All @@ -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()
};
}
}
2 changes: 1 addition & 1 deletion tests/unit/src/flixel/system/macros/FlxAssetPathsTest.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

0 comments on commit 9d47edb

Please # to comment.