├── .gitignore ├── LICENSE.txt ├── README.md ├── example.hxml ├── example ├── Example.hx ├── broken.json ├── broken.xml ├── pack │ ├── BaseA.hx │ ├── BaseB.hx │ ├── InterfaceC.hx │ ├── sub1 │ │ ├── Sub1TypeANumber1.hx │ │ ├── Sub1TypeANumber2.hx │ │ ├── Sub1TypeBNumber1.hx │ │ └── Sub1TypeBNumber2.hx │ └── sub2 │ │ ├── Sub2TypeANumber1.hx │ │ ├── Sub2TypeANumber2.hx │ │ ├── Sub2TypeBNumber1.hx │ │ └── Sub2TypeBNumber2.hx ├── test.json ├── test.md ├── test.txt ├── test.xml └── test.yaml ├── haxelib.json ├── release.sh └── src ├── CompileTime.hx └── CompileTimeClassList.hx /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Files 2 | *.js 3 | *.n 4 | *.swf 5 | *.zip 6 | bin/ 7 | 8 | # Editor Files 9 | *.sublime-* 10 | .haxelib 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jason O'Neil 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Compile Time 2 | ============ 3 | 4 | Simple Haxe Macro Helpers that let you do or get things at compile-time. 5 | 6 | Usage 7 | ----- 8 | 9 | ```haxe 10 | // Compile date and time 11 | 12 | var date = CompileTime.buildDate(); // Equivalent of writing `new Date(2012,11,25,20,48,15);` 13 | var dateAsString = CompileTime.buildDateString(); // A string saying "2012-12-25 20:48:15" 14 | 15 | // Compile git commit sha 16 | 17 | var sha = CompileTime.buildGitCommitSha(); 18 | //'104ad4e' 19 | 20 | // Read a file 21 | 22 | var file = CompileTime.readFile("README.md"); // will be compiled as an ordinary string in your code 23 | 24 | // Read a file and use String Interpolation 25 | 26 | var name="Jason", age=25; 27 | var greeting = CompileTime.interpolateFile("test.txt"); 28 | // Reads the contents of test.txt, and interpolates local values, similar to single quote string interpolation 29 | // Same as writing greeting = 'Hello my name is $name and I am ${age-5} years old' 30 | // Result will be "Hello my name is Jason and I am 20 years old"; 31 | 32 | // Import a whole package to make sure it's included in compilation. Does not affect dead-code-elimination. 33 | 34 | CompileTime.importPackage("server.controllers"); 35 | 36 | // Read a file, and check it is valid XML. Will give a compile time error if it's not XML. 37 | 38 | var xmlString = CompileTime.readXmlFile("haxelib.xml"); // Will insert it as a String. 39 | Xml.parse(xmlString); 40 | 41 | // Read a file, and check it is valid JSON. Will give a compile time error if it's not valid Json. 42 | 43 | var jsonString = CompileTime.readJsonFile("test.json"); // Inserts the contents of text.json as a String 44 | 45 | // Parse the JSON file, so it is inserted as an object declaration into the code. 46 | // This has the added benefit of giving you compile time typing and autocompletion. 47 | 48 | var jsonObject = CompileTime.parseJsonFile("test.json"); 49 | var typedJsonObject:Person = CompileTime.parseJsonFile("test.json"); // Same as above, but check the result matches a typedef 50 | 51 | // Parse the JSON file, so it is inserted as an object declaration into the code. 52 | // This has the added benefit of giving you compile time typing and autocompletion. 53 | 54 | var yamlObject = CompileTime.parseYamlFile("test.yaml"); 55 | var yamlObject:Person = CompileTime.parseYamlFile("test.yaml"); // Check the type is correct. 56 | 57 | // Read a markdown file, convert to HTML and check the result is valid XML. Will give a compile time error if it doesn't validate. 58 | 59 | var htmlString = CompileTime.readMarkdownFile("test.md"); // Will insert it as a HTML String. 60 | Xml.parse(htmlString); 61 | 62 | // Get lists of classes that have been compiled 63 | 64 | // Returns a list of all the classes in the "my.package" package, including sub-packages 65 | CompileTime.getAllClasses("my.package"); 66 | 67 | // Returns a list of only the classes in the "my.package" package, so not including sub-packages 68 | CompileTime.getAllClasses("my.package", false); 69 | 70 | // Returns a list of all the classes that inherit MySuperClass, no matter what package 71 | CompileTime.getAllClasses(MySuperClass); 72 | 73 | // Returns a list of all the classes in the "my.package" package that inherit MySuperClass. 74 | CompileTime.getAllClasses("my.package", MySuperClass); 75 | 76 | // Returns a list of all the classes that implement MyInterface, no matter what package. 77 | CompileTime.getAllClasses(MyInterface); 78 | 79 | // Returns a list of all the classes in the "my.package" package that implement MyInterface. 80 | CompileTime.getAllClasses("my.package", MyInterface); 81 | 82 | ``` 83 | -------------------------------------------------------------------------------- /example.hxml: -------------------------------------------------------------------------------- 1 | -main Example 2 | -lib markdown 3 | -lib yaml 4 | -cp src/ 5 | -cp example/ 6 | 7 | --each 8 | -neko example/Example.hx.n 9 | 10 | --next 11 | -js example/Example.hx.js 12 | 13 | --next 14 | -swf example/Example.hx.swf 15 | -------------------------------------------------------------------------------- /example/Example.hx: -------------------------------------------------------------------------------- 1 | import pack.*; 2 | import pack.sub1.*; 3 | import pack.sub2.*; 4 | 5 | using Lambda; 6 | 7 | typedef Person = { 8 | name:String, 9 | age:Int, 10 | pets:Array, 11 | ?other:Int 12 | } 13 | 14 | class Example 15 | { 16 | static var myObj = CompileTime.parseJsonFile("test.json"); 17 | 18 | static function main() 19 | { 20 | var date = CompileTime.buildDate(); // Equivalent of writing `new Date(2012,11,25,20,48,15);` 21 | var dateAsString = CompileTime.buildDateString(); // A string saying "2012-12-25 20:48:15" 22 | var gitsha = CompileTime.buildGitCommitSha(); // A string that might say '104ad4e' 23 | var file = CompileTime.readFile("README.md"); // Reads the contents of README.md as a String. 24 | var name="Jason", age=25; 25 | var greeting = CompileTime.interpolateFile("test.txt"); // Reads the contents of test.txt, and interpolates local values, similar to single quotes 26 | var xmlString = CompileTime.readXmlFile("test.xml"); // Reads the contents of text.xml as a String, but checks that it is valid XML 27 | // var xmlString = CompileTime.readXmlFile("broken.xml"); 28 | var markdownHTML = CompileTime.readMarkdownFile("test.md"); // Reads the contents of text.xml as a String, but checks that it is valid XML 29 | var jsonString = CompileTime.readJsonFile("test.json"); // Reads the contents of text.json as a String, but checks that it is valid JSON 30 | // var jsonString = CompileTime.readJsonFile("broken.json"); 31 | var jsonObject = CompileTime.parseJsonFile("test.json"); // Reads the contents of text.json, parses it, and places the resulting object in the code so no parsing happens at runtime 32 | var typedJsonObject:Person = CompileTime.parseJsonFile("test.json"); // Same as above, but check the result matches our typedef 33 | 34 | var yamlObject = CompileTime.parseYamlFile("test.yaml"); // Reads the contents of test.yaml, parses it, and places the resulting object in the code so no parsing happens at runtime 35 | 36 | myObj; // Set from static variable: that's pretty cool! 37 | 38 | CompileTime.importPackage("pack"); // Imports every class in that package 39 | 40 | var allClasses = CompileTime.getAllClasses(); // Get every class that is compiled. You probably don't ever want this. 41 | assert(allClasses.count() > 10); 42 | 43 | var packClasses = CompileTime.getAllClasses("pack"); // Get every class in package "pack" 44 | assertEquals(10, packClasses.count()); 45 | 46 | var packClassesOnly = CompileTime.getAllClasses("pack", false); // Get every class in package "pack", but ignore sub-packages 47 | assertEquals(2, packClassesOnly.count()); 48 | 49 | var packSub1Classes = CompileTime.getAllClasses("pack.sub1"); // Get every class in package "pack.sub1" 50 | assertEquals(4, packSub1Classes.count()); 51 | 52 | var packSub2Classes = CompileTime.getAllClasses("pack.sub2"); // Get every class in package "pack.sub2" 53 | assertEquals(4, packSub2Classes.count()); 54 | 55 | var packSubClasses = CompileTime.getAllClasses("pack.sub"); // Get every class in package "pack.sub" (not "pack.sub1" or "pack.sub2") - verify exact package name matching. 56 | assertEquals(0, packSubClasses.count()); 57 | 58 | var baseAClasses = CompileTime.getAllClasses(BaseA); // Get every class that inherits BaseA, no matter which package 59 | assertEquals(4, baseAClasses.count()); 60 | 61 | var baseBClasses = CompileTime.getAllClasses(BaseB); // Get every class that inherits BaseB, no matter which package 62 | assertEquals(4, baseBClasses.count()); 63 | 64 | var baseBClasses = CompileTime.getAllClasses(pack.BaseB); // You can also use a fully qualified class name 65 | assertEquals(4, baseBClasses.count()); 66 | 67 | var baseBPackSub1Classes = CompileTime.getAllClasses("pack.sub1", BaseB); // Get every class in package "pack.sub1" that inherits BaseB 68 | assertEquals(2, baseBPackSub1Classes.count()); 69 | 70 | for (c in baseBPackSub1Classes) 71 | { 72 | var o = Type.createInstance(c, []); 73 | trace(o.b); // sub1.1, sub1.2 74 | } 75 | 76 | var interfaceCClasses = CompileTime.getAllClasses(InterfaceC); // Get every class that implements interface C. 77 | assertEquals(6, interfaceCClasses.count()); 78 | 79 | var interfaceCClasses = CompileTime.getAllClasses("pack.sub1", InterfaceC); // Get every class that implements interface C. 80 | assertEquals(3, interfaceCClasses.count()); 81 | } 82 | 83 | static function assertEquals(expected:T, actual:T, ?pos:haxe.PosInfos) { 84 | if (expected != actual) haxe.Log.trace('Failure (Expected $expected, actual $actual)',pos); 85 | } 86 | 87 | static function assert (condition:Bool, ?pos:haxe.PosInfos) 88 | { 89 | if (condition == false) haxe.Log.trace('Failure',pos); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /example/broken.json: -------------------------------------------------------------------------------- 1 | { 2 | broken json 3 | } -------------------------------------------------------------------------------- /example/broken.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/pack/BaseA.hx: -------------------------------------------------------------------------------- 1 | package pack; 2 | 3 | @:keepSub 4 | class BaseA 5 | { 6 | var a:String; 7 | 8 | public function new() 9 | { 10 | a = "Base"; 11 | } 12 | } -------------------------------------------------------------------------------- /example/pack/BaseB.hx: -------------------------------------------------------------------------------- 1 | package pack; 2 | 3 | @:keepSub 4 | class BaseB implements InterfaceC 5 | { 6 | public var b:String; 7 | 8 | public function new() 9 | { 10 | b = "Base"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/pack/InterfaceC.hx: -------------------------------------------------------------------------------- 1 | package pack; 2 | 3 | /* 4 | * Get "getAllClasses()" to also search for interfaces (they're also Class) 5 | * While we're at it, fix the casting to List? 6 | * Write some tests (interface C, implemented by sub1.Sub1TypeANumber1 and sub2TypeBNumber2) 7 | * Create UFAdminModule interface (w/ build macro to check constructor args) 8 | * in UFAdminController, use getAllClasses( UFAdminModule ) interface 9 | * Create an EasyAuthAdminController (placeholder for now) 10 | * List users 11 | * Show user, list permission 12 | * Move DBAdminController to ORM? 13 | */ 14 | interface InterfaceC { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /example/pack/sub1/Sub1TypeANumber1.hx: -------------------------------------------------------------------------------- 1 | package pack.sub1; 2 | 3 | class Sub1TypeANumber1 extends pack.BaseA implements pack.InterfaceC 4 | { 5 | public function new() 6 | { 7 | super(); 8 | a = "sub1.1"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/pack/sub1/Sub1TypeANumber2.hx: -------------------------------------------------------------------------------- 1 | package pack.sub1; 2 | 3 | class Sub1TypeANumber2 extends pack.BaseA 4 | { 5 | public function new() 6 | { 7 | super(); 8 | a = "sub1.2"; 9 | } 10 | } -------------------------------------------------------------------------------- /example/pack/sub1/Sub1TypeBNumber1.hx: -------------------------------------------------------------------------------- 1 | package pack.sub1; 2 | 3 | class Sub1TypeBNumber1 extends pack.BaseB 4 | { 5 | public function new() 6 | { 7 | super(); 8 | b = "sub1.1"; 9 | } 10 | } -------------------------------------------------------------------------------- /example/pack/sub1/Sub1TypeBNumber2.hx: -------------------------------------------------------------------------------- 1 | package pack.sub1; 2 | 3 | class Sub1TypeBNumber2 extends pack.BaseB 4 | { 5 | public function new() 6 | { 7 | super(); 8 | b = "sub1.2"; 9 | } 10 | } -------------------------------------------------------------------------------- /example/pack/sub2/Sub2TypeANumber1.hx: -------------------------------------------------------------------------------- 1 | package pack.sub2; 2 | 3 | class Sub2TypeANumber1 extends pack.BaseA 4 | { 5 | public function new() 6 | { 7 | super(); 8 | a = "sub2.1"; 9 | } 10 | } -------------------------------------------------------------------------------- /example/pack/sub2/Sub2TypeANumber2.hx: -------------------------------------------------------------------------------- 1 | package pack.sub2; 2 | 3 | class Sub2TypeANumber2 extends pack.BaseA 4 | { 5 | public function new() 6 | { 7 | super(); 8 | a = "sub2.2"; 9 | } 10 | } -------------------------------------------------------------------------------- /example/pack/sub2/Sub2TypeBNumber1.hx: -------------------------------------------------------------------------------- 1 | package pack.sub2; 2 | 3 | class Sub2TypeBNumber1 extends pack.BaseB 4 | { 5 | public function new() 6 | { 7 | super(); 8 | b = "sub2.1"; 9 | } 10 | } -------------------------------------------------------------------------------- /example/pack/sub2/Sub2TypeBNumber2.hx: -------------------------------------------------------------------------------- 1 | package pack.sub2; 2 | 3 | class Sub2TypeBNumber2 extends pack.BaseB 4 | { 5 | public function new() 6 | { 7 | super(); 8 | b = "sub2.2"; 9 | } 10 | } -------------------------------------------------------------------------------- /example/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Jason", 3 | "age": 26, 4 | "pets": ["Theo", "Darcy", "Calamity"] 5 | } -------------------------------------------------------------------------------- /example/test.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | **This is the future.** I hope you like it. -------------------------------------------------------------------------------- /example/test.txt: -------------------------------------------------------------------------------- 1 | Hello my name is $name and I am ${age-5} years old -------------------------------------------------------------------------------- /example/test.xml: -------------------------------------------------------------------------------- 1 | 2 | My Title 3 | -------------------------------------------------------------------------------- /example/test.yaml: -------------------------------------------------------------------------------- 1 | invoice: 34843 2 | date : 2001-01-23 3 | bill_to: &id001 4 | given : Chris 5 | family : Dumars 6 | address: 7 | lines: | 8 | 458 Walkman Dr. 9 | Suite #292 10 | city : Royal Oak 11 | state : MI 12 | postal : 48046 13 | ship_to: *id001 14 | tax : 251.42 15 | 457: true 16 | total: 4443.52 17 | comments: > 18 | Late afternoon is best. 19 | Backup contact is Nancy 20 | Billsmer @ 338-4338. 21 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compiletime", 3 | "license": "MIT", 4 | "classPath": "src/", 5 | "tags": ["cross","macro","helpers"], 6 | "description": "Simple Haxe Macro Helpers that let you do or get things at compile-time. ", 7 | "contributors": ["jason", "andyli", "kevinresol"], 8 | "releasenote": "Fix for Haxe 4", 9 | "version": "2.8.0", 10 | "url": "https://github.com/jasononeil/compiletime", 11 | "dependencies": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f compiletime.zip 4 | zip -r compiletime.zip example.hxml example haxelib.json LICENSE.txt README.md src 5 | -------------------------------------------------------------------------------- /src/CompileTime.hx: -------------------------------------------------------------------------------- 1 | /**** 2 | * Copyright (c) 2013 Jason O'Neil 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | * 10 | ****/ 11 | 12 | import haxe.macro.Context; 13 | import haxe.macro.Expr; 14 | import haxe.macro.Type; 15 | import haxe.macro.Format; 16 | import haxe.Json; 17 | #if yaml 18 | import yaml.Yaml; 19 | import yaml.Parser; 20 | import yaml.Renderer; 21 | import yaml.util.ObjectMap; 22 | #end 23 | using StringTools; 24 | using Lambda; 25 | 26 | class CompileTime 27 | { 28 | /** Inserts a date object of the date and time that this was compiled */ 29 | macro public static function buildDate():ExprOf { 30 | var date = Date.now(); 31 | var year = toExpr(date.getFullYear()); 32 | var month = toExpr(date.getMonth()); 33 | var day = toExpr(date.getDate()); 34 | var hours = toExpr(date.getHours()); 35 | var mins = toExpr(date.getMinutes()); 36 | var secs = toExpr(date.getSeconds()); 37 | return macro new Date($year, $month, $day, $hours, $mins, $secs); 38 | } 39 | 40 | /** Returns a string of the date and time that this was compiled */ 41 | macro public static function buildDateString():ExprOf { 42 | return toExpr(Date.now().toString()); 43 | } 44 | 45 | /** Returns a string of the current git sha1 */ 46 | macro public static function buildGitCommitSha():ExprOf { 47 | var proc = new sys.io.Process('git', ['log', "--pretty=format:'%h'", '-n', '1']); 48 | var sha1 = proc.stdout.readLine(); 49 | return toExpr(sha1); 50 | } 51 | 52 | /** Reads a file at compile time, and inserts the contents into your code as a string. The file path is resolved using `Context.resolvePath`, so it will search all your class paths */ 53 | macro public static function readFile(path:String):ExprOf { 54 | return toExpr(loadFileAsString(path)); 55 | } 56 | 57 | /** Reads a file at compile time, and inserts the contents into your code as an interpolated string, similar to using 'single $quotes'. */ 58 | macro public static function interpolateFile(path:String):ExprOf { 59 | return Format.format( toExpr(loadFileAsString(path)) ); 60 | } 61 | 62 | /** Same as readFile, but checks that the file is valid Json */ 63 | macro public static function readJsonFile(path:String):ExprOf { 64 | var content = loadFileAsString(path); 65 | try Json.parse(content) catch (e:Dynamic) { 66 | haxe.macro.Context.error('Json from $path failed to validate: $e', Context.currentPos()); 67 | } 68 | return toExpr(content); 69 | } 70 | 71 | /** Same as readFile, but checks that the file is valid Json */ 72 | macro public static function parseJsonFile(path:String):ExprOf<{}> { 73 | var content = loadFileAsString(path); 74 | var obj = try Json.parse(content) catch (e:Dynamic) { 75 | haxe.macro.Context.error('Json from $path failed to validate: $e', Context.currentPos()); 76 | } 77 | return toExpr(obj); 78 | } 79 | 80 | #if yaml 81 | macro public static function parseYamlFile(path:String) { 82 | var content = loadFileAsString(path); 83 | var data = Yaml.parse(content, Parser.options().useObjects()); 84 | var s = haxe.Json.stringify(data); 85 | var json = haxe.Json.parse(s); 86 | return toExpr(json); 87 | } 88 | #end 89 | 90 | 91 | /** Same as readFile, but checks that the file is valid Xml */ 92 | macro public static function readXmlFile(path:String):ExprOf { 93 | var content = loadFileAsString(path); 94 | try Xml.parse(content) catch (e:Dynamic) { 95 | haxe.macro.Context.error('Xml from $path failed to validate: $e', Context.currentPos()); 96 | } 97 | return toExpr(content); 98 | } 99 | 100 | #if markdown 101 | /** Same as readFile, but checks that the file is valid Xml */ 102 | macro public static function readMarkdownFile(path:String):ExprOf { 103 | var content = loadFileAsString(path); 104 | try { 105 | content = Markdown.markdownToHtml( content ); 106 | Xml.parse(content); 107 | } catch (e:Dynamic) { 108 | haxe.macro.Context.error('Markdown from $path did not produce valid XML: $e', Context.currentPos()); 109 | } 110 | return toExpr(content); 111 | } 112 | #end 113 | 114 | /** Import a package at compile time. Is a simple mapping to haxe.macro.Compiler.include(), but means you don't have to wrap your code in conditionals. */ 115 | macro public static function importPackage(path:String, ?recursive:Bool = true, ?ignore : Array, ?classPaths : Array) { 116 | haxe.macro.Compiler.include(path, recursive, ignore, classPaths); 117 | return toExpr(0); 118 | } 119 | 120 | /** Returns an Array of Classes. By default it will return all classes, but you can also search for classes in a particular package, 121 | classes that extend a particular type, and you can choose whether to look for classes recursively or not. */ 122 | macro public static function getAllClasses(?inPackage:String, ?includeChildPackages:Bool = true, ?extendsBaseClass:ExprOf>):ExprOf>> { 123 | 124 | // Add the onGenerate function to search for matching classes and add them to our metadata. 125 | // Make sure we run it once per-compile, not once per-controller-per-compile. 126 | // Also ensure that it is re-run for each new compile if using the compiler cache. 127 | #if (haxe_ver < 4.0) 128 | Context.onMacroContextReused(function () { 129 | allClassesSearches = new Map(); 130 | return true; 131 | }); 132 | #end 133 | if ( Lambda.count(allClassesSearches)==0 ) { 134 | Context.onGenerate(checkForMatchingClasses); 135 | } 136 | 137 | // Add the search to our static var so we can get results during onGenerate 138 | var baseClass:ClassType = getClassTypeFromExpr(extendsBaseClass); 139 | var baseClassName:String = (baseClass == null) ? "" : baseClass.pack.join('.') + '.' + baseClass.name; 140 | var listID = '$inPackage,$includeChildPackages,$baseClassName'; 141 | allClassesSearches[listID] = { 142 | inPackage: inPackage, 143 | includeChildPackages: includeChildPackages, 144 | baseClass: baseClass 145 | }; 146 | 147 | if (extendsBaseClass!=null) 148 | return macro CompileTimeClassList.getTyped($v{listID}, $extendsBaseClass); 149 | else 150 | return macro CompileTimeClassList.get($v{listID}); 151 | } 152 | 153 | #if macro 154 | static function toExpr(v:Dynamic) { 155 | return Context.makeExpr(v, Context.currentPos()); 156 | } 157 | 158 | static function loadFileAsString(path:String) { 159 | try { 160 | var p = Context.resolvePath(path); 161 | Context.registerModuleDependency(Context.getLocalModule(),p); 162 | return sys.io.File.getContent(p); 163 | } 164 | catch(e:Dynamic) { 165 | return haxe.macro.Context.error('Failed to load file $path: $e', Context.currentPos()); 166 | } 167 | } 168 | 169 | static function isSameClass(a:ClassType, b:ClassType):Bool { 170 | return ( 171 | a.pack.join(".") == b.pack.join(".") 172 | && a.name == b.name 173 | ); 174 | } 175 | 176 | static function implementsInterface(cls:ClassType, interfaceToMatch:ClassType):Bool { 177 | while (cls!=null) { 178 | for ( i in cls.interfaces ) { 179 | if (isSameClass(i.t.get(), interfaceToMatch)) { 180 | return true; 181 | } 182 | } 183 | if (cls.superClass!=null) { 184 | cls = cls.superClass.t.get(); 185 | } 186 | else cls = null; 187 | } 188 | return false; 189 | } 190 | 191 | static function isSubClassOfBaseClass(subClass:ClassType, baseClass:ClassType):Bool { 192 | var cls = subClass; 193 | while (cls.superClass != null) 194 | { 195 | cls = cls.superClass.t.get(); 196 | if (isSameClass(baseClass, cls)) { return true; } 197 | } 198 | return false; 199 | } 200 | 201 | static function getClassTypeFromExpr(e:Expr):ClassType { 202 | var ct:ClassType = null; 203 | var fullClassName = null; 204 | var parts = new Array(); 205 | var nextSection = e.expr; 206 | while (nextSection != null) { 207 | // Break the loop unless we explicitly encounter a next section... 208 | var s = nextSection; 209 | nextSection = null; 210 | 211 | switch (s) { 212 | // Might be a direct class name, no packages 213 | case EConst(c): 214 | switch (c) { 215 | case CIdent(s): 216 | if (s != "null") parts.unshift(s); 217 | default: 218 | } 219 | // Might be a fully qualified package name 220 | // { expr => EField({ expr => EField({ expr => EConst(CIdent(sys)), pos => #pos(src/server/Server.hx:35: characters 53-56) },db), pos => #pos(src/server/Server.hx:35: characters 53-59) },Object), pos => #pos(src/server/Server.hx:35: characters 53-66) } 221 | case EField(e, field): 222 | parts.unshift(field); 223 | nextSection = e.expr; 224 | default: 225 | } 226 | } 227 | fullClassName = parts.join("."); 228 | if (fullClassName != "") { 229 | switch (Context.follow(Context.getType(fullClassName))) { 230 | case TInst(classType, _): 231 | ct = classType.get(); 232 | default: 233 | throw "Currently CompileTime.getAllClasses() can only search by package name or base class, not interface, typedef etc."; 234 | } 235 | } 236 | return ct; 237 | } 238 | 239 | static var allClassesSearches:Map = new Map(); 240 | static function checkForMatchingClasses(allTypes:Array) { 241 | // Prepare a map to store our results. 242 | var getAllClassesResult:Map> = new Map(); 243 | for (listID in allClassesSearches.keys()) { 244 | getAllClassesResult[listID] = []; 245 | } 246 | 247 | // Go through all the types and look for matches. 248 | for (type in allTypes) { 249 | switch type { 250 | // We only care for Classes 251 | case TInst(t, _): 252 | var className = t.toString(); 253 | var classType = t.get(); 254 | if (t.get().isInterface==false) { 255 | // Check if this class matches any of our searches. 256 | for (listID in allClassesSearches.keys()) { 257 | var search = allClassesSearches[listID]; 258 | if (classMatchesSearch(className,classType,search)) { 259 | getAllClassesResult[listID].push(className); 260 | } 261 | } 262 | } 263 | default: 264 | } 265 | } 266 | 267 | // Add the results to some metadata so it's available at runtime. 268 | switch (Context.getType("CompileTimeClassList")) { 269 | case TInst(classType, _): 270 | var ct = classType.get(); 271 | // Get rid of any existing metadata (if using the compiler cache) 272 | if (ct.meta.has('classLists')) 273 | ct.meta.remove('classLists'); 274 | // Add the class names to CompileTimeClassList as metadata 275 | var classListsMetaArray:Array = []; 276 | for (listID in getAllClassesResult.keys()) { 277 | var classNames = getAllClassesResult[listID]; 278 | var itemAsArray = macro [$v{listID}, $v{classNames.join(",")}]; 279 | classListsMetaArray.push(itemAsArray); 280 | } 281 | ct.meta.add('classLists', classListsMetaArray, Context.currentPos()); 282 | default: 283 | } 284 | 285 | return; 286 | } 287 | 288 | static function classMatchesSearch(className:String, classType:ClassType, search:CompileTimeClassSearch):Bool { 289 | // Check if it belongs to a certain package or subpackage 290 | if (search.inPackage != null) { 291 | if (search.includeChildPackages) { 292 | if (className.startsWith(search.inPackage + ".") == false) 293 | return false; 294 | } 295 | else { 296 | var re = new EReg("^" + search.inPackage + "\\.([A-Z][a-zA-Z0-9]*)$", ""); 297 | if (re.match(className) == false) 298 | return false; 299 | } 300 | } 301 | 302 | // Check if it is a subclass of a certain type 303 | if (search.baseClass != null) { 304 | if (search.baseClass.isInterface) { 305 | if (implementsInterface(classType, search.baseClass) == false) 306 | return false; 307 | } 308 | else { 309 | if (isSubClassOfBaseClass(classType, search.baseClass) == false) 310 | return false; 311 | } 312 | } 313 | 314 | return true; 315 | } 316 | #end 317 | } 318 | 319 | #if macro 320 | typedef CompileTimeClassSearch = { 321 | inPackage:String, 322 | includeChildPackages:Bool, 323 | baseClass:ClassType 324 | } 325 | #end 326 | -------------------------------------------------------------------------------- /src/CompileTimeClassList.hx: -------------------------------------------------------------------------------- 1 | /**** 2 | * Copyright (c) 2013 Jason O'Neil 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | * 10 | ****/ 11 | 12 | class CompileTimeClassList 13 | { 14 | static var lists:Map>> = null; 15 | 16 | public static function get(id:String):List> 17 | { 18 | if (lists == null) initialise(); 19 | return lists.get(id); 20 | } 21 | 22 | public static inline function getTyped(id:String, type:Class):List> 23 | { 24 | return cast get(id); 25 | } 26 | 27 | static function initialise() 28 | { 29 | lists = new Map(); 30 | var m = haxe.rtti.Meta.getType(CompileTimeClassList); 31 | if (m.classLists != null) 32 | { 33 | for (item in m.classLists) 34 | { 35 | var array:Array = cast item; 36 | var listID = array[0]; 37 | var list = new List(); 38 | for ( typeName in array[1].split(',') ) { 39 | var type = Type.resolveClass(typeName); 40 | if ( type!=null ) list.push( type ); 41 | } 42 | lists.set(listID, list); 43 | } 44 | } 45 | } 46 | } 47 | --------------------------------------------------------------------------------