From fbad23b9f663bc78d9de15a1403ec686e1d70efc Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Fri, 24 Mar 2023 17:05:59 -0700
Subject: [PATCH] First pass at prefab tool (done)

---
 src/dawntools/prefabtool/PrefabTool.cpp | 85 +++++++++++++++++++++++--
 src/dawntools/prefabtool/PrefabTool.hpp |  6 +-
 src/dawntools/util/Directory.cpp        | 34 +++++-----
 src/dawntools/util/Directory.hpp        |  3 +-
 4 files changed, 101 insertions(+), 27 deletions(-)

diff --git a/src/dawntools/prefabtool/PrefabTool.cpp b/src/dawntools/prefabtool/PrefabTool.cpp
index 8aa70830..980fafc1 100644
--- a/src/dawntools/prefabtool/PrefabTool.cpp
+++ b/src/dawntools/prefabtool/PrefabTool.cpp
@@ -32,8 +32,6 @@ struct PrefabComponentParserRuleset PrefabRegistry::getRuleset(std::string type)
           if(!file.readString(&data)) {
             std::cout << "Failed to read ruleset file!" << file.filename << std::endl;
           } else {
-            std::cout << "EPIC!" << file.filename << std::endl;
-
             // Begin scanning contentsr
             // std::string include = this->sources;
             auto toRemove = File::normalizeSlashes(this->sources + "/");
@@ -48,11 +46,43 @@ struct PrefabComponentParserRuleset PrefabRegistry::getRuleset(std::string type)
             struct PrefabComponentParserRuleset ruleset;
             ruleset.include = includePath;
             ruleset.name = type;
-            
 
+            // Find each instance of "@optional" when it's used within a comment
+            // e.g. // @optional or /* @optional */ in the string data.
+            std::regex regex("^(\\s)*(\\/\\/|\\/\\*\\s*)(\\@optional)(\\s*\\*\\/)?(\\s)$", std::regex_constants::ECMAScript);
+            std::sregex_iterator it(data.begin(), data.end(), regex);
+            std::sregex_iterator end;
 
-            // return;
-            assertUnreachable();
+            while(it != end) {
+              std::smatch match = *it;
+
+              auto nextLineStart = data.rfind('\n', match.position() + match.length()) + 1;
+              auto nextLineEnd = data.find('\n', nextLineStart);
+              auto nextLine = data.substr(nextLineStart, nextLineEnd - nextLineStart);
+
+              std::regex regex2("^\\s*([^\\s]+)\\s+([^\\s;]+)\\s*;", std::regex_constants::ECMAScript);
+              std::sregex_iterator it2(nextLine.begin(), nextLine.end(), regex2);
+              std::sregex_iterator end2;
+              while(it2 != end2) {
+                // Log out all matches
+                std::smatch match2 = *it2;
+                std::string type = match2[1].str();
+                std::string name = match2[2].str();
+
+                // Type may be StateProperty<type>, so we need to extract the type
+                auto regex3 = std::regex("^StateProperty<([^>]+)>$", std::regex_constants::ECMAScript);
+                std::smatch match3;
+                if(std::regex_match(type, match3, regex3)) {
+                  type = match3[1].str();
+                }
+
+                // Set a parser for this type
+                ruleset.optional[name] = type;
+                ++it2;
+              }
+              ++it;
+            }
+            return ruleset;
           }
         }
       }
@@ -192,13 +222,54 @@ int32_t PrefabComponentParser::onParse(
   out->include = ruleset.include;
   out->type = node->node;
 
+  // Define parser types here.
+  auto rawParser = [&](std::string v){
+    return v;
+  };
+
+  auto stringParser = [&](std::string v){
+    return "\"" + v + "\"";
+  };
+
+  auto floatParser = [&](std::string v){
+    // Make sure number contains decimal
+    if(v.find(".") == std::string::npos) {
+      v += ".0";
+    }
+    // Make sure number contains a number before the decimal
+    if(v.find(".") == 0) {
+      v = "0" + v;
+    }
+    // Make sure ends with f
+    if(v.find("f") == std::string::npos) {
+      v += "f";
+    }
+    return v;
+  };
+
+
   // Iterate all the optional attributes and store within the out if present
   auto itOptional = ruleset.optional.begin();
   while(itOptional != ruleset.optional.end()) {
+    // Determine the parser to use
+    auto type = itOptional->second;
+
+    std::function<std::string(std::string)> parser = rawParser;
+    if(type.find("string") != std::string::npos) {
+      parser = stringParser;
+    } else if(type.find("float") != std::string::npos) {
+      parser = floatParser;
+    } else if(type.find("*") == (type.size() - 1)) {
+      type = type.substr(0, type.size() - 1);
+      parser = rawParser;
+    } else {
+      *error = "Unkown parser for type: " + type;
+      return 1;
+    }
+
     if(node->attributes.find(itOptional->first) != node->attributes.end()) {
       auto raw = node->attributes[itOptional->first];
-      auto parsed = itOptional->second(raw);
-      out->values[itOptional->first] = parsed;
+      out->values[itOptional->first] = parser(raw);
     }
     ++itOptional;
   }
diff --git a/src/dawntools/prefabtool/PrefabTool.hpp b/src/dawntools/prefabtool/PrefabTool.hpp
index f65880e3..7b44b1e2 100644
--- a/src/dawntools/prefabtool/PrefabTool.hpp
+++ b/src/dawntools/prefabtool/PrefabTool.hpp
@@ -11,10 +11,14 @@
 #include <regex>
 
 namespace Dawn {
+  enum PrefabcComponentParserRulesetType {
+    STRIN
+  };
+
   struct PrefabComponentParserRuleset {
     std::string name;
     std::string include;
-    std::map<std::string, std::function<std::string(std::string)>> optional;
+    std::map<std::string, std::string> optional;
   };
 
   struct PrefabRegistry {
diff --git a/src/dawntools/util/Directory.cpp b/src/dawntools/util/Directory.cpp
index 8bbc2bdd..225248c5 100644
--- a/src/dawntools/util/Directory.cpp
+++ b/src/dawntools/util/Directory.cpp
@@ -12,24 +12,24 @@ Directory::Directory(std::string dirname) {
 }
 
 bool_t Directory::open() {
-  if(this->handle) return true;
-  this->handle = opendir(this->filename.c_str());
-  return this->handle != nullptr;
+  if(!this->exists()) return false;
+  if (handle != std::filesystem::end(handle)) return true;
+  handle = std::filesystem::directory_iterator(filename);
+  return handle != std::filesystem::end(handle);
 }
 
 bool_t Directory::isOpen() {
-  return this->handle != nullptr;
+  return handle != std::filesystem::end(handle);
 }
 
 void Directory::close() {
-  if(this->handle) {
-    closedir(this->handle);
-    this->handle = nullptr;
+  if(this->isOpen()) {
+    handle = std::filesystem::directory_iterator();
   }
 }
 
 bool_t Directory::exists() {
-  return this->open();
+  return std::filesystem::exists(filename);
 }
 
 void Directory::mkdirp() {
@@ -37,20 +37,18 @@ void Directory::mkdirp() {
 }
 
 std::map<std::string, enum DirectoryChildType> Directory::readDirectory() {
+  this->close();
   if(!this->open()) return {};
+
   std::map<std::string, enum DirectoryChildType> children;
-
-  struct dirent *dp;
-  while((dp = readdir(this->handle)) != NULL) {
-    if(!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
-      continue;
+  for(const auto& entry : handle) {
+    if (entry.is_directory()) {
+      children[entry.path().filename().string()] = DIRECTORY_CHILD_TYPE_DIRECTORY;
+    } else if (entry.is_regular_file()) {
+      children[entry.path().filename().string()] = DIRECTORY_CHILD_TYPE_FILE;
     }
-
-    //Skip anything that isn't a file or directory (symlinks, etc.)
-    if(dp->d_type != DT_DIR && dp->d_type != DT_REG) continue;
-    std::string fn = std::string(dp->d_name);
-    children[fn] = dp->d_type == DT_DIR ? DIRECTORY_CHILD_TYPE_DIRECTORY : DIRECTORY_CHILD_TYPE_FILE;
   }
+
   this->close();
   return children;
 }
diff --git a/src/dawntools/util/Directory.hpp b/src/dawntools/util/Directory.hpp
index fcf686d3..991a2d09 100644
--- a/src/dawntools/util/Directory.hpp
+++ b/src/dawntools/util/Directory.hpp
@@ -5,6 +5,7 @@
 
 #pragma once
 #include "util/File.hpp"
+#include <filesystem>
 
 namespace Dawn {
   enum DirectoryChildType {
@@ -15,7 +16,7 @@ namespace Dawn {
   class Directory {
     public:
       std::string filename;
-      DIR *handle = nullptr;
+      std::filesystem::directory_iterator handle;
 
       Directory(std::string dir);