A small library to help you create classes.
Requires Haxe 4.
Inspired by hamaluik/haxe-partials.
What it does is just copying fields from a class to another, using the @:autoBuild
macro.
Completes the constructor of any class according to non-initialized variables.
Similar to this, but some enhancements as below:
- Ignores variables that already have initial values.
- Respects and integrates existing constructor (if exists).
- Source code with every function commented
- Friendly debug log mode (see "Compiler flags" below)
- This is nothing but reinventing the wheel!
- Developed within a few days and haven't considered many usecases yet
Prepare a class that you want to use as a component of another class.
Implement ripper.Spirit
interface here.
class Attacker implements ripper.Spirit {
public function attack() {
trace('Player attacked!');
}
}
Then create a class which actually uses the component above.
Implement ripper.Body
interface here,
and specify which class(es) to use in the @:ripper.spirits
metadata.
@:ripper.spirits(Attacker)
class Player implements ripper.Body {
// This class doesn't have the method attack()
// but it will be copied from the Attacker class
}
Now every field of the Spirit class (Attacker
here) is copied to the Body class (Player
here).
class Main {
static function main() {
final player = new Player();
player.attack();
}
}
Finally the output below
(at default this library prints some [INFO] logs during the compilation.
See "Compiler flags" below for details).
[INFO] Player.hx | Player | Copied fields: Player <= Attacker
Attacker.hx:3: Player attacked!
- It can also have multiple parameters.
- You can write multiple
@:ripper.spirits()
lines as well. - A module subclass can also be specified.
@:ripper.spirits(my_pkg.Attacker, my_pkg.Magician)
@:ripper.spirits(my_pkg.Attacker.HardAttacker)
class Player implements ripper.Body { /* ... */ }
The classes can be specified with:
- Absolute package path, or
- Relative package path from the current package
(however the parent packages cannot be referred.
Only the classes in the same package or its sub-packages).
If you are using completion server, sometimes it might go wrong and raise odd errors due to the reusing of macro context.
In that case you may have to reboot it manually (if VSCode, >Haxe: Restart Language Server
).
To share the same fields between Body
/Spirit
classes,
create another base class that has the fields, and let Body
/Spirit
classes extend it.
Although the fields of Spirit
class are copied to Body
class,
fields of super-classes are not copied by this process,
thus you can avoid "Duplicate class field declaration" errors here.
By attaching @:ripper.override
metadata to any field in a Spirit
class,
you can force it to be copied to Body
class even if the Body
already has another field with the same name.
By adding @:ripper.verified
metadata to a class that implements Body
or Spirit
,
you can suppress INFO/DEBUG logs and some validations regardless of the compiler flag settings.
At default, a class that implements Spirit
is excluded from compilation as it is unlikely that it will be used alone.
Add @:ripper.preserve
metadata to the class to avoid this.
If any type is used with import
(or using
) in a Spirit
class,
the module of Body
class should also import it.
Otherwise you will have "Unresolved identifier" errors.
By implementing ripper.Data
interface, the constructor is automatically completed.
For example, this:
class MyData implements ripper.Data {
public final a: Int;
public final b: Float;
public final c: String;
public final d: Int = 0; // Will be ignored as already initialized
}
compiles to:
class MyData implements ripper.Data {
public final a: Int;
public final b: Float;
public final c: String;
public final d: Int = 0;
public function new(a: Int, b: Float, c: String) {
this.a = a;
this.b = b;
this.c = c;
}
}
If you add metadata @:ripper.callSuper
to your data class, a super constructor call without arguments (super()
) is automatically injected.
This is only available if the super constructor has no mandatory arguments; otherwise you have to declare new()
and call super(args)
manually.
library | flag | description |
---|---|---|
ripper | ripper_validation_disable | Disables all validation during the compilation. |
sneaker | sneaker_macro_log_level | Less than 300 for hiding WARN/INFO/DEBUG logs. 500 or more for showing all logs. |
sneaker | sneaker_macro_message_level | Similar to above, but related to compiler messages. Not related to DEBUG logs. |
With the flag sneaker_macro_log_level
you can also see more detailed log messages when compiling.
If 500
or more (this will include all DEBUG logs), for example:
[DEBUG] Initialize Spirit fields map. (ripper.macro.SpiritMacro::fieldsMap)
[DEBUG] Player.hx | Player | Start to build Body class.
[DEBUG] Player.hx | Player | Start to process metadata parameter: Attacker
[DEBUG] Player.hx | Player | Start to search type: Attacker
[DEBUG] Player.hx | Player | Resolving module...
[DEBUG] Player.hx | Player | Attacker => Found.
[DEBUG] Player.hx | Player | Found type: Attacker
[DEBUG] Player.hx | Player | Resolving as a class.
[DEBUG] Attacker.hx | Attacker | Start registration of Spirit fields.
[DEBUG] Attacker.hx | Attacker | Registered Spirit fields for copying to Body.
[DEBUG] Attacker.hx | Attacker | Exclude this type from compilation. End registration.
[DEBUG] Player.hx | Player | Resolved type as a class: Attacker
[DEBUG] Player.hx | Player | Copying fields...
[DEBUG] Player.hx | Player | - attack
[INFO] Player.hx | Player | Copied fields: Player <= Attacker
[DEBUG] Player.hx | Player | End building.
See also: FAL Haxe libraries