Browse Source

Working on atomic-cli and AtomicTool

Josh Engebretson 10 years ago
parent
commit
b5ec3d028c

+ 1 - 0
CLI/.gitignore

@@ -0,0 +1 @@
+Bin/*

+ 62 - 1
CLI/cli.js

@@ -1,4 +1,65 @@
 #!/usr/bin/env node
 "use strict";
 
- console.log("\n\nThe atomic-cli is currently in development, please see http://www.AtomicGameEngine.com for details\n\n");
+var argparse = require("argparse");
+var fs = require("fs");
+var path = require("path");
+var util = require("util");
+var httpreq = require("httpreq");
+var cli = require("atomic-cli");
+
+var AtomicHelpFormatter = function (opts) {
+    argparse.HelpFormatter.call(this, opts);
+};
+util.inherits(AtomicHelpFormatter, argparse.HelpFormatter);
+
+// http://stackoverflow.com/questions/13423540/argparse-subparser-hide-metavar-in-command-listing
+AtomicHelpFormatter.prototype._formatAction = function (action) {
+    var parts = argparse.HelpFormatter.prototype._formatAction.call(this, action);
+    if (action.nargs == argparse.Const.PARSER) {
+        var lines = parts.split("\n");
+        lines.shift();
+        parts = lines.join("\n");
+    }
+    return parts;
+};
+
+var catchErrors = function (promise) {
+    promise.catch(function (error) {
+        if (Array.isArray(error)) error = error[0]; // NCP throws an array of errors...?
+        if (error) console.error(error.message || error);
+        process.exit(1);
+    });
+};
+
+var parser = new argparse.ArgumentParser({prog: "atomic-cli", formatterClass: AtomicHelpFormatter,
+    description: "Atomic Game Engine CLI"});
+parser.addArgument(["-v", "--version"], {action: "version", help: "Print version and exit.",
+    version: cli.VERSION});
+
+var commands = parser.addSubparsers({title: "Commands", metavar: "<command>"});
+
+var cmd = commands.addParser("new", {help: "Create a new project.",
+    description: "Creates a new project at the given path.",
+    aliases: ["create"]});
+cmd.addArgument(["path"], {help: "The new project directory to create."});
+cmd.setDefaults({action: function (args) {
+    cli.newProject(args.path)
+    .then(function () {
+        console.log("New Atomic project created in " + path.resolve(args.path));
+    })
+    .catch(function (error) {
+        console.error("Error: Could not create " + path.resolve(args.path));
+        process.exit(1);
+    });
+}});
+
+// GO!
+if (process.argv.length > 2) {
+    var args = parser.parseArgs();
+    args.action(args);
+} else {
+    parser.printHelp();
+}
+
+// spawn(cli.ATOMIC_TOOL_BIN, { stdio: 'inherit' });

+ 57 - 0
CLI/index.js

@@ -0,0 +1,57 @@
+
+var Q = require("q");
+var fs = require("fs");
+var ncp = require("ncp").ncp;
+var os = require("os");
+var path = require("path");
+var spawn = require("child_process").spawn;
+var wrench = require("wrench");
+
+var DATA_DIR = __dirname + "/data/";
+exports.DATA_DIR = DATA_DIR;
+
+var ATOMIC_TOOL_BIN = __dirname + "/data/bin/Mac/AtomicTool";
+
+exports.VERSION = JSON.parse(fs.readFileSync(__dirname + "/package.json")).version;
+
+var exec = function (command, flags, opts) {
+    opts = opts || {};
+    if (opts.verbose !== false) {
+        console.log([command].concat(flags).join(" "));
+    }
+
+    // Run everything through cmd.exe on Windows to be able to find .bat files
+    if (process.platform == "win32" && opts.windowsCmd !== false) {
+        flags.unshift("/c", command);
+        command = "cmd";
+    }
+
+    var deferred = Q.defer();
+    var child = spawn(command, flags, {stdio: (opts.output === false) ? "ignore" : "inherit"});
+    child.on("close", function (code) {
+        if (code && opts.check !== false) {
+            deferred.reject();
+        }
+        deferred.resolve(code);
+    });
+    child.on("error", function (error) {
+        deferred.reject(error);
+    });
+    return deferred.promise;
+};
+exports.exec = exec;
+
+
+var atomictool = function (flags, opts) {
+    opts = opts || {};
+    opts.windowsCmd = false;
+
+    flags.unshift(DATA_DIR);
+    flags.unshift("--cli-data-path");
+    return exec(ATOMIC_TOOL_BIN, flags, opts);
+};
+exports.atomictool = atomictool
+
+exports.newProject = function (output) {
+  return atomictool(["new", output], {output:true});
+};;

+ 13 - 2
CLI/package.json

@@ -9,7 +9,18 @@
     "url": "https://github.com/AtomicGameEngine/AtomicGameEngine/issues"
   },
   "dependencies": {
-    "websocket": "~1.0.8"
+    "adm-zip": "~0.4.3",
+    "argparse": "~0.1.15",
+    "connect": "~2.8.3",
+    "js-yaml": "~2.1.0",
+    "ncp": "~0.4.2",
+    "open": "~0.0.3",
+    "q": "~0.9.6",
+    "watch": "~0.8.0",
+    "websocket": "~1.0.8",
+    "wrench": "~1.5.1",
+    "xmldom": "~0.1.16",
+    "httpreq": "*"
   },
   "description": "CLI for the Atomic Game Engine",
   "engines": {
@@ -25,4 +36,4 @@
   "preferGlobal": true,
   "repository": "https://github.com/AtomicGameEngine/AtomicGameEngine",
   "version": "0.2.4"
-}
+}

+ 54 - 5
Source/AtomicTool/AtomicTool.cpp

@@ -4,8 +4,9 @@
 #include <Atomic/Engine/Engine.h>
 
 #include <ToolCore/ToolSystem.h>
-
 #include <ToolCore/License/LicenseSystem.h>
+#include <ToolCore/Command/Command.h>
+#include <ToolCore/Command/CommandParser.h>
 
 #include "AtomicTool.h"
 
@@ -33,26 +34,74 @@ void AtomicTool::Setup()
 
     for (unsigned i = 0; i < arguments.Size(); ++i)
     {
-        if (arguments[i].Length() > 1 /* && arguments[i][0] == '-'*/)
+        if (arguments[i].Length() > 1)
         {
-            String argument = arguments[i].Substring(1).ToLower();
+            String argument = arguments[i].ToLower();
             String value = i + 1 < arguments.Size() ? arguments[i + 1] : String::EMPTY;
 
-            LOGINFOF("%s", argument.CString());
+            if (argument == "--cli-data-path")
+            {
+                if (!value.Length())
+                    ErrorExit("Unable to parse --cli-data-path");
+
+                cliDataPath_ = value;
+            }
         }
+
     }
 
+    if (!cliDataPath_.Length())
+        ErrorExit("Unable to parse --data-path");
+
     engineParameters_["Headless"] = true;
     engineParameters_["ResourcePaths"] = "";
 }
 
+void AtomicTool::HandleCommandFinished(StringHash eventType, VariantMap& eventData)
+{
+    GetSubsystem<Engine>()->Exit();
+}
+
+void AtomicTool::HandleCommandError(StringHash eventType, VariantMap& eventData)
+{
+    String error = "Command Error";
+
+    const String& message = eventData[CommandError::P_MESSAGE].ToString();
+    if (message.Length())
+        error = message;
+
+    ErrorExit(error);
+}
+
 void AtomicTool::Start()
 {
-    context_->RegisterSubsystem(new ToolSystem(context_));
+    // Subscribe to events
+    SubscribeToEvent(E_COMMANDERROR, HANDLER(AtomicTool, HandleCommandError));
+    SubscribeToEvent(E_COMMANDFINISHED, HANDLER(AtomicTool, HandleCommandFinished));
+
+    const Vector<String>& arguments = GetArguments();
+
+    ToolSystem* tsystem = new ToolSystem(context_);
+    context_->RegisterSubsystem(tsystem);
+    tsystem->SetDataPath(cliDataPath_);
+
+    SharedPtr<CommandParser> parser(new CommandParser(context_));
+    SharedPtr<Command> cmd(parser->Parse(arguments));
+    if (!cmd)
+    {
+        String error = "No command found";
+
+        if (parser->GetErrorMessage().Length())
+            error = parser->GetErrorMessage();
+
+        ErrorExit(error);
+    }
 
     // BEGIN LICENSE MANAGEMENT
     GetSubsystem<LicenseSystem>()->Initialize();
     // END LICENSE MANAGEMENT
+
+    cmd->Run();
 }
 
 void AtomicTool::Stop()

+ 11 - 0
Source/AtomicTool/AtomicTool.h

@@ -5,6 +5,10 @@
 
 using namespace Atomic;
 
+#include <ToolCore/Command/Command.h>
+
+using namespace ToolCore;
+
 namespace AtomicTool
 {
 
@@ -26,6 +30,13 @@ public:
 
 private:
 
+    void HandleCommandFinished(StringHash eventType, VariantMap& eventData);
+    void HandleCommandError(StringHash eventType, VariantMap& eventData);
+
+    String cliDataPath_;
+
+    SharedPtr<Command> command_;
+
 };
 
 }

+ 1 - 1
Source/AtomicTool/CMakeLists.txt

@@ -4,4 +4,4 @@ add_executable(AtomicTool ${ATOMIC_TOOL_SOURCES})
 
 target_link_libraries(AtomicTool ${ATOMIC_LINK_LIBRARIES} ToolCore Poco curl)
 
-
+add_dependencies(AtomicTool AtomicEditor)

+ 2 - 2
Source/CMakeLists.txt

@@ -5,9 +5,9 @@ add_subdirectory(AtomicJS)
 add_subdirectory(AtomicPlayer)
 
 if (NOT IOS AND NOT ANDROID AND NOT EMSCRIPTEN)    
-	add_subdirectory(ToolCore)
-	add_subdirectory(AtomicTool)
+    add_subdirectory(ToolCore)
     add_subdirectory(AtomicEditor)
+    add_subdirectory(AtomicTool)
     add_subdirectory(Tools)
 endif()
 

+ 30 - 0
Source/ToolCore/Command/Command.cpp

@@ -0,0 +1,30 @@
+#include "Command.h"
+
+namespace ToolCore
+{
+
+Command::Command(Context* context) : Object(context),
+    timeOut_(0.0f)
+{
+
+}
+
+Command::~Command()
+{
+
+}
+
+void Command::Error(const String& errorMsg)
+{
+    VariantMap eventData;
+    eventData[CommandError::P_MESSAGE] = errorMsg;
+    SendEvent(E_COMMANDERROR, eventData);
+}
+
+void Command::Finished()
+{
+    SendEvent(E_COMMANDFINISHED);
+}
+
+}
+

+ 47 - 0
Source/ToolCore/Command/Command.h

@@ -0,0 +1,47 @@
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+EVENT(E_COMMANDERROR, CommandError)
+{
+    PARAM(P_MESSAGE, Message);      // string
+}
+
+
+EVENT(E_COMMANDFINISHED, CommandFinished)
+{
+
+}
+
+class Command : public Object
+{
+    OBJECT(Command);
+
+public:
+
+    Command(Context* context);
+    virtual ~Command();
+
+    virtual bool Parse(const Vector<String>& arguments, unsigned startIndex, String& errorMsg) = 0;
+
+    virtual void Run() = 0;
+
+    virtual void Finished();
+
+    virtual void Error(const String& errorMsg);
+
+    virtual void Cancel() {}
+
+private:
+
+    float timeOut_;
+
+};
+
+}

+ 47 - 0
Source/ToolCore/Command/CommandParser.cpp

@@ -0,0 +1,47 @@
+
+#include "CommandParser.h"
+
+#include "NewProjectCmd.h"
+
+namespace ToolCore
+{
+
+CommandParser::CommandParser(Context* context) : Object(context)
+{
+
+}
+
+CommandParser::~CommandParser()
+{
+
+}
+
+Command* CommandParser::Parse(const Vector<String>& arguments)
+{
+    Command* cmd = NULL;
+
+    for (unsigned i = 0; i < arguments.Size(); ++i)
+    {
+        if (arguments[i].Length())
+        {
+            String argument = arguments[i].ToLower();
+
+            if (argument == "new")
+            {
+                cmd = new NewProjectCmd(context_);
+            }
+        }
+
+        if (cmd)
+        {
+            if (cmd->Parse(arguments, i, errorMsg_))
+                return cmd;
+        }
+
+    }
+
+    return NULL;
+}
+
+}
+

+ 32 - 0
Source/ToolCore/Command/CommandParser.h

@@ -0,0 +1,32 @@
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+class Command;
+
+class CommandParser : public Object
+{
+    OBJECT(CommandParser);
+
+public:
+
+    CommandParser(Context* context);
+    virtual ~CommandParser();
+
+    Command* Parse(const Vector<String>& arguments);
+
+    const String& GetErrorMessage() const { return errorMsg_; }
+
+private:
+
+    String errorMsg_;
+
+};
+
+}

+ 59 - 0
Source/ToolCore/Command/NewProjectCmd.cpp

@@ -0,0 +1,59 @@
+
+#include <Atomic/Core/StringUtils.h>
+#include <Atomic/IO/Log.h>
+
+#include "NewProjectCmd.h"
+
+#include <Poco/File.h>
+
+namespace ToolCore
+{
+
+NewProjectCmd::NewProjectCmd(Context* context) : Command(context)
+{
+
+}
+
+NewProjectCmd::~NewProjectCmd()
+{
+
+}
+
+bool NewProjectCmd::Parse(const Vector<String>& arguments, unsigned startIndex, String& errorMsg)
+{
+    String argument = arguments[startIndex].ToLower();
+    String value = startIndex + 1 < arguments.Size() ? arguments[startIndex + 1] : String::EMPTY;
+
+    if (argument != "new")
+    {
+        errorMsg = "Unable to parse new command";
+        return false;
+    }
+
+    if (!value.Length())
+    {
+        errorMsg = "Unable to parse new project path";
+        return false;
+    }
+
+    projectPath_ = value;
+
+    return true;
+}
+
+void NewProjectCmd::Run()
+{
+    Poco::File projectDest(projectPath_.CString());
+
+    if (projectDest.exists())
+    {
+        Error(ToString("New project path: %s already exists", projectPath_.CString()));
+        return;
+    }
+
+    LOGINFOF("Creating new project in: %s", projectPath_.CString());
+
+    Finished();
+}
+
+}

+ 30 - 0
Source/ToolCore/Command/NewProjectCmd.h

@@ -0,0 +1,30 @@
+
+#pragma once
+
+#include "Command.h"
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+class NewProjectCmd: public Command
+{
+    OBJECT(NewProjectCmd);
+
+public:
+
+    NewProjectCmd(Context* context);
+    virtual ~NewProjectCmd();
+
+    bool Parse(const Vector<String>& arguments, unsigned startIndex, String& errorMsg);
+
+    void Run();
+
+private:
+
+    String projectPath_;
+
+};
+
+}