Khaled Garbaya

Engineering Leader, Software Developer & Educator

Create a cross-platform command line tool in haxe

To Help you getting started with haxe I had this idea about creating a simple (Hello world) cross-platform command line tool which is really simple and it took me not more than 30 min.

So you maybe want a super awesome command line tool to help you automate some process generate files etc… and you want it cross platform , Haxe got you covered.

I choose to build the app targeting Neko VM which is the goto platform to build command line tools.

Let’s go through the classes and explain them.

1/** 2*@author kgarbaya 3**/ 4import net.khaledgarbaya.processor.CmdProcessor; 5import net.khaledgarbaya.helper.LogHelper; 6class CommandLineTool 7{ 8 /// parse commands and perform action 9 private var processor:CmdProcessor; 10 11 /// this is how haxe will know that this is the main class 12 public static function main() : Void 13 { 14 var interpreter = new CommandLineTool(); 15 interpreter.run(); 16 } 17 public function new() 18 { 19 processor = new CmdProcessor(); 20 } 21 public function run():Void 22 { 23 var command = getCommandFromSysArgs(); 24 try 25 { 26 var ret = processor.process(command,Sys.args()); 27 if( ret != null ) 28 LogHelper.println(ret+"\n"); 29 } 30 catch (ex:CmdError) 31 { 32 LogHelper.error("Unknown Command"); 33 } 34 } 35 36 public function getCommandFromSysArgs() : String 37 { 38 for(arg in Sys.args()) 39 { 40 if(arg.charAt(0) == "-") 41 { 42 /// here you handle command config like -arg 43 } 44 else 45 { 46 return arg; 47 } 48 } 49 50 return "help"; 51 } 52} 53

This is the main class you cann tell that by the static function main so haxe when compiling will know that this is the main class. CommandLineTool class what it does is getting all the args from Sys.args which is the argument passed after your executable name for example : $mycmdtool arg1 arg2 ....., give the args to a command process that will process these args and devide them to command and arg and then execute the command mapped to the command passed by the user if it found a match. See the code bellow.

1/** 2*@author kgarbaya 3**/ 4package net.khaledgarbaya.processor; 5import net.khaledgarbaya.helper.LogHelper; 6import net.khaledgarbaya.command.SayHelloCommand; 7import net.khaledgarbaya.command.impl.ICommand; 8using Lambda; 9using StringTools; 10enum CmdError 11{ 12 IncompleteStatement; 13 InvalidStatement(msg :String); 14} 15 16class CmdProcessor 17{ 18 /** connecting command to a specific function */ 19 public static var commands : List<{ name : String, doc : String, command : ICommand }>; 20 private var currentTime:Float; 21 22 public function new() 23 { 24 commands = new List(); 25 26 addCommand('hello', SayHelloCommand.helpString, new SayHelloCommand()); 27 } 28 29 function addCommand( name, doc, command ) : Void 30 { 31 commands.add({ name : name, doc : doc, command : command }); 32 } 33/** 34 * process a line of user input 35 **/ 36 public function process(cmd : String, args : Array<String>) :String 37 { 38 var output:String; 39 if( cmd.endsWith('\\') ) 40 { 41 throw "IncompleteStatement"; 42 } 43 44 /** If the command is help **/ 45 if( cmd == 'help' ) 46 { 47 return printHelp(); 48 } 49 50 /** Other commands **/ 51 for( c in commands ) 52 { 53 if( c.name == cmd ) 54 { 55 currentTime = Date.now().getTime(); 56 output = c.command.execute(cmd, args); 57 LogHelper.println(' Time passed '+((Date.now().getTime()-currentTime)/1000)+' sec for command "$cmd"'); 58 return output; 59 } 60 } 61 return 'Command ' + cmd + ' Not Found, try to type help for more info'; 62 } 63 64 public static function printHelp() :String 65 { 66 var ret : String = 'Awesome Shell \n'; 67 68 for (c in commands) 69 { 70 ret += '\n--------------------------\n\n' + c.doc ; 71 } 72 ret += '\n--------------------------\n'; 73 return ret; 74 } 75 76 77} 78

each Command is basically a class that implements the ICommand Interface which contains only one method execute(cmd,args):String , but you can add more if you want for example undo and redo etc.For the purpose of the demo I just created a simple command to print in the shell hello with the current time.

1/** 2*@author kgarbaya 3**/ 4package net.khaledgarbaya.command; 5 6import net.khaledgarbaya.command.impl.ICommand; 7class SayHelloCommand implements ICommand 8{ 9 public static var helpString : String = "hello , Just Saying Hello with the current date"; 10 public function new() 11 {} 12 public function execute(cmd : String, args:Array<String>) : String 13 { 14 return "Hello the date is : " + Date.now().toString(); 15 } 16 17} 18

After your command line app is done what you need to do is build using neko.

1neko Build.hxml 2

the Build.hxml actually is a shortcut used instead of typing a lot of command you just list them in this file and Haxe will execute them.

1-main CommandLineTool 2-neko ../bin/cmd_tool.n 3

If you want to test the app navigate to the bin folder with your terminal and execute the folowing command

1neko cmd_tool.n hello 2

If you don't want to use Neko any more you can create a native excuteable for it using this command

1nekotools boot cmd_tool.n 2

you can find the full source on github here , feel free to fork and play around and let me know about your awesome command line tool.

Cheers,

Khaled

Next one in your inbox

no spam only content