documentor: new econ-based documentation format. add ear-to-econ-ecdoc tool to migrat...
authorRejean Loyer <redj@ecere.com>
Sat, 5 Dec 2015 02:07:02 +0000 (21:07 -0500)
committerJerome St-Louis <jerome@ecere.com>
Thu, 28 Jul 2016 22:23:17 +0000 (18:23 -0400)
documentor/src/Documentor.ec
documentor/tools/ear-to-econ-ecdoc.ec [new file with mode: 0644]
documentor/tools/ear-to-econ-ecdoc.epj [new file with mode: 0644]

index 3d1a72c..f9dd351 100644 (file)
@@ -1,4 +1,5 @@
 import "ecere"
+#if !defined(EAR_TO_ECON_ECDOC)
 import "ec"
 import "HTMLView"
 import "IDESettings"
@@ -471,6 +472,7 @@ public:
 
 enum DocumentationType
 {
+   unset,
    nameSpaceDoc,
    classDoc,
    functionDoc,
@@ -479,6 +481,7 @@ enum DocumentationType
 
 enum DocumentationItem
 {
+   unset,
    description,
    usage,
    remarks,
@@ -495,14 +498,60 @@ enum DocumentationItem
 
 static void FigureFileName(char * fileName, Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
 {
+   char hex[20];
+   fileName[0] = 0;
+   sprintf(hex, "%p", module);
+   strcat(fileName, hex);
+   strcat(fileName, "/");
+   sprintf(hex, "%p", object);
+   strcat(fileName, hex);
+   strcat(fileName, "/");
+   sprintf(hex, "%p", data);
+   strcat(fileName, hex);
+   strcat(fileName, "/");
+   if(type == nameSpaceDoc)
+      strcat(fileName, "namespace");
+   else if(type == functionDoc)
+      strcat(fileName, "function");
+   else if(type == classDoc)
+      strcat(fileName, "class");
+   else if(type == methodDoc)
+      strcat(fileName, "method");
+   strcat(fileName, "/");
+   if(item == description)
+      strcat(fileName, "description");
+   else if(item == usage)
+      strcat(fileName, "usage");
+   else if(item == remarks)
+      strcat(fileName, "remarks");
+   else if(item == example)
+      strcat(fileName, "example");
+   else if(item == seeAlso)
+      strcat(fileName, "seeAlso");
+   else if(item == enumerationValue)
+      strcat(fileName, "enumerationValue");
+   else if(item == definition)
+      strcat(fileName, "definition");
+   else if(item == conversion)
+      strcat(fileName, "conversion");
+   else if(item == memberDescription)
+      strcat(fileName, "memberDescription");
+   else if(item == propertyDescription)
+      strcat(fileName, "propertyDescription");
+   else if(item == parameter)
+      strcat(fileName, "parameter");
+   else if(item == returnValue)
+      strcat(fileName, "returnValue");
+}
+
+static void FigureFilePath(char * path, Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
+{
+   char docPath[MAX_LOCATION];
    NameSpace * nameSpace, * ns;
    Class cl = null;
    Method method = null;
    GlobalFunction function = null;
    char nsName[1024], temp[1024];
-   char docFile[1024];
-
-
    switch(type)
    {
       case nameSpaceDoc: nameSpace = object; break;
@@ -512,126 +561,262 @@ static void FigureFileName(char * fileName, Module module, DocumentationType typ
    }
 
    nsName[0] = 0;
+   temp[0] = 0;
    ns = nameSpace;
    while(ns && ns->name)
    {
-      strcpy(temp, "namespaces/");
-      strcat(temp, ns->name);
+      strcpy(temp, ns->name);
       strcat(temp, "/");
       strcat(temp, nsName);
       strcpy(nsName, temp);
       ns = ns->parent;
    }
-   sprintf(docFile, "%s.eCdoc", (!module || !module.name || !strcmp(nsName, "namespaces/ecere/namespaces/com")) ? "ecereCOM" : module.name);
-   if(strchr(docFile, DIR_SEP))
-   {
-      GetLastDirectory(docFile, temp);
-      strcpy(docFile, temp);
-   }
-
-   sprintf(fileName, "<%s/%s>", settings.docDir, docFile); // Note that in the ecereIDE.ini file, there can be no quotes around the path, and there needs to be the final backslash. Otherwise this does not work.
-   strcat(fileName, nsName);
 
+   docPath[0] = 0;
+   PathCatSlash(docPath, (!module || !module.name || !strcmp(nsName, "namespaces/ecere/namespaces/com")) ? "ecereCOM" : module.name);
+   //ChangeExtension(docPath, "eCdoc", docPath);
+   PathCatSlash(docPath, nsName);
    if(cl)
    {
-      strcat(fileName, "classes/");
-      strcat(fileName, cl.name);
-      strcat(fileName, "/");
-   }
-
-   if(method)
-   {
-      strcat(fileName, "methods/");
-      strcat(fileName, method.name);
-      strcat(fileName, "/");
-   }
-   else if(function)
-   {
-      const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
-      if(name) name += 2; else name = function.name;
-      strcat(fileName, "functions/");
-      strcat(fileName, name);
-      strcat(fileName, "/");
-   }
-
-   switch(item)
-   {
-      case description: strcat(fileName, "description"); break;
-      case usage: strcat(fileName, "usage"); break;
-      case remarks: strcat(fileName, "remarks"); break;
-      case example: strcat(fileName, "example"); break;
-      case seeAlso: strcat(fileName, "seeAlso"); break;
-      case returnValue: strcat(fileName, "returnValue"); break;
-      case enumerationValue:
-         strcat(fileName, "enumeration values/");
-         strcat(fileName, ((NamedLink)data).name);
-         break;
-      case definition:
-         strcat(fileName, "definitions/");
-         strcat(fileName, ((Definition)data).name);
-         break;
-      case conversion:
-      {
-         const char * name = RSearchString(((Property)data).name, "::", strlen(((Property)data).name), true, false);
-         if(name) name += 2; else name = ((Property)data).name;
-         strcat(fileName, "conversions/");
-         strcat(fileName, name);
-         break;
-      }
-      case memberDescription:
-         strcat(fileName, "data members/");
-         strcat(fileName, ((DataMember)data).name);
-         break;
-      case propertyDescription:
-         strcat(fileName, "properties/");
-         strcat(fileName, ((Property)data).name);
-         break;
-      case parameter:
-      {
-         int count;
-         char name[1024];
-         Type prev;
-         strcat(fileName, "parameters/");
-         for(prev = data, count = 0; prev; prev = prev.prev, count++);
-         sprintf(name, "%s.%d", ((Type)data).name, count);
-         strcat(fileName, name);
-         break;
-      }
+      char * name = getDocFileNameFromTypeName(cl.name);
+      PathCatSlash(docPath, name);
+      delete name;
    }
+   else
+      PathCatSlash(docPath, "_global-defs");
+   ChangeExtension(docPath, "econ", docPath);
+
+   path[0] = 0;
+   strcpy(path, settings.docDir);
+   PathCatSlash(path, docPath);
 }
 
 static char * ReadDoc(Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
 {
-   char fileName[MAX_LOCATION];
    String contents = null;
-   File file;
+   bool docRetrieved = false;
+   NamespaceDoc nsDoc = null;
+   ClassDoc clDoc = null;
+   FunctionDoc fnDoc = null;
+   MethodDoc mdDoc = null;
+   String s = null;
+   char filePath[MAX_LOCATION];
+   Method method = null;
+   GlobalFunction function = null;
 
-   FigureFileName(fileName, module, type, object, item, data);
-   file = FileOpen(fileName, read);
-   if(file)
+   ItemDoc doc = getDoc(filePath, module, type, object, item, data, false);
+
+   switch(type)
    {
-      uint len;
-      if((len = file.GetSize()))
+      case functionDoc:  function = object; break;
+      case methodDoc:    method = object; break;
+   }
+
+   if(doc)
+   {
+      if(eClass_IsDerived(doc._class, class(ClassDoc)))
       {
-         contents = new char[len+1];
-         file.Read(contents, 1, len);
-         contents[len] = '\0';
+         clDoc = (ClassDoc)doc;
+         docRetrieved = true;
+      }
+      else if(eClass_IsDerived(doc._class, class(NamespaceDoc)))
+      {
+         nsDoc = (NamespaceDoc)doc;
+         docRetrieved = true;
       }
-      delete file;
    }
-   if(contents)
+
+   if(docRetrieved)
    {
-      int c;
-      for(c = 0; contents[c]; c++)
-         if(!isspace(contents[c])) break;
-      if(!contents[c])
-         delete contents;
+      ItemDoc itDoc = null;
+      if(type == functionDoc)
+      {
+         MapIterator<String, FunctionDoc> it { map = nsDoc.functions };
+         const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
+         if(name) name += 2; else name = function.name;
+         if(it.Index(name, false))
+            fnDoc = it.data;
+      }
+      else if(type == methodDoc)
+      {
+         MapIterator<String, MethodDoc> it { map = clDoc.methods };
+         if(it.Index(method.name, false))
+            mdDoc = it.data;
+      }
+
+      switch(item)
+      {
+         case description: s = type == methodDoc ? mdDoc.description : type == functionDoc ? fnDoc.description : type == classDoc ? clDoc.description : nsDoc.description; break;
+         case usage: s = type == methodDoc ? mdDoc.usage : type == functionDoc ? fnDoc.usage : type == classDoc ? clDoc.usage : null; break;
+         case remarks: s = type == methodDoc ? mdDoc.remarks : type == functionDoc ? fnDoc.remarks : type == classDoc ? clDoc.remarks : null; break;
+         case example: s = type == methodDoc ? mdDoc.example : type == functionDoc ? fnDoc.example : type == classDoc ? clDoc.example : null; break;
+         case seeAlso: s = type == methodDoc ? mdDoc.also : type == functionDoc ? fnDoc.also : type == classDoc ? clDoc.also : null; break;
+         case returnValue: s = type == methodDoc ? mdDoc.returnValue : type == functionDoc ? fnDoc.returnValue : null; break;
+         case enumerationValue:
+            if(clDoc && clDoc.values)
+            {
+               itDoc = clDoc.values[((NamedLink)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case definition:
+            if(nsDoc && nsDoc.defines)
+            {
+               itDoc = nsDoc.defines[((Definition)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case conversion:
+            if(clDoc && clDoc.conversions)
+            {
+               const char * name = RSearchString(((Property)data).name, "::", strlen(((Property)data).name), true, false);
+               if(name) name += 2; else name = ((Property)data).name;
+               itDoc = clDoc.conversions[name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case memberDescription:
+            if(clDoc && clDoc.fields)
+            {
+               itDoc = clDoc.fields[((DataMember)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case propertyDescription:
+            if(clDoc && clDoc.properties)
+            {
+               itDoc = clDoc.properties[((Property)data).name];
+               if(itDoc) s = itDoc.description;
+            }
+            break;
+         case parameter:
+            if((type == functionDoc && fnDoc && fnDoc.parameters) || (type == methodDoc && mdDoc && mdDoc.parameters))
+            {
+               Type prev;
+               char * name;
+               for(prev = data; prev; prev = prev.prev);
+               name = ((Type)data).name;
+               if(type == functionDoc)
+               {
+                  itDoc = fnDoc.parameters[name];
+                  if(itDoc) s = itDoc.description;
+               }
+               else if(type == methodDoc)
+               {
+                  itDoc = mdDoc.parameters[name];
+                  if(itDoc) s = itDoc.description;
+               }
+            }
+            break;
+      }
+      if(s)
+         contents = CopyString(s);
    }
    if(editing && !contents && !readOnly)
       contents = CopyString($"[Add Text]");
    return contents;
 }
 
+ItemDoc getDoc(char * filePath, Module module, DocumentationType type, void * object, DocumentationItem item, void * data, bool create)
+{
+   ItemDoc doc = null;
+   bool docRetrieved = false;
+   File f;
+   NamespaceDoc nsDoc = null;
+   ClassDoc clDoc = null;
+
+   char docPath[MAX_LOCATION];
+   Class cl = null;
+   Method method = null;
+
+   switch(type)
+   {
+      case classDoc:     cl = (Class)object; break;
+      case methodDoc:    method = object; cl = method._class; break;
+   }
+
+   FigureFilePath(filePath, module, type, object, item, data);
+
+   if(docCache[filePath])
+   {
+      DocCacheEntry entry = docCache[docPath];
+      if(cl && eClass_IsDerived(entry.doc._class, class(ClassDoc)))
+      {
+         clDoc = (ClassDoc)entry.doc;
+         docRetrieved = true;
+      }
+      else if(!cl && eClass_IsDerived(entry.doc._class, class(NamespaceDoc)))
+      {
+         nsDoc = (NamespaceDoc)entry.doc;
+         docRetrieved = true;
+      }
+   }
+
+   if(!docRetrieved)
+   {
+      f = FileOpen(filePath, read);
+      if(f)
+      {
+         JSONParser parser { f = f, eCON = true };
+         JSONResult jsonResult;
+         jsonResult = parser.GetObject(cl ? class(ClassDoc) : class(NamespaceDoc), cl ? &clDoc : &nsDoc);
+         delete parser;
+         delete f;
+
+         if(jsonResult == success)
+         {
+            docRetrieved = true;
+            docCache[docPath] = { added = GetTime(), doc = cl ? clDoc : nsDoc };
+         }
+         else
+         {
+            PrintLn("error: problem parsing file: ", filePath);
+            delete clDoc;
+            delete nsDoc;
+         }
+      }
+   }
+
+   if(!docRetrieved)
+   {
+      if(cl)
+         clDoc = { };
+      else
+         nsDoc = { };
+      doc = cl ? clDoc : nsDoc;
+      docCache[docPath] = { added = GetTime(), doc = cl ? clDoc : nsDoc };
+      docRetrieved = true;
+   }
+   else
+   {
+      doc = cl ? clDoc : nsDoc;
+   }
+
+   //void pruneDocCache()
+   {
+      MapIterator<const String, DocCacheEntry> it { map = docCache };
+      Array<const String> toRemove { };
+      Time now = GetTime();
+      for(entry : docCache)
+      {
+         Time diff = now - entry.added;
+         if(diff > 60*0.5)
+            toRemove.Add(&entry);
+      }
+      while(toRemove.count)
+      {
+         if(it.Index(toRemove.lastIterator.data, false))
+         {
+            delete it.data;
+            it.Remove();
+         }
+         toRemove.Remove(toRemove.lastIterator.pointer);
+      }
+      delete toRemove;
+   }
+   return doc;
+}
+
 class APIPageNameSpace : APIPage
 {
    NameSpace * nameSpace;
@@ -2486,31 +2671,27 @@ class HelpView : HTMLView
       {
          // Writability test
          {
-            char docFile[MAX_LOCATION];
-            Archive archive;
-            Module module = page ? page.GetModule() : null;
-            NameSpace * ns = page ? page.GetNameSpace() : null;
-
-            sprintf(docFile, "%s/%s.eCdoc", settings.docDir, (!module || !module.name || (ns && ns->name && !strcmp(ns->name, "namespaces/ecere/namespaces/com"))) ? "ecereCOM" : module.name);
-            if(FileExists(docFile))
-            {
-               archive = ArchiveOpen(docFile, { true } );
-               readOnly = archive == null;
-               delete archive;
-            }
-            else
+            char docDir[MAX_LOCATION];
+            readOnly = true;
+            strcpy(docDir, settings.docDir);
+            if(FileExists(docDir).isDirectory)
             {
-               readOnly = true;
-               archive = ArchiveOpen(docFile, { true } );
-               if(archive)
+               PathCatSlash(docDir, "___docWriteTest");
+               if(FileExists(docDir).isDirectory)
+               {
+                  RemoveDir(docDir);
+                  if(!FileExists(docDir))
+                     readOnly = false;
+               }
+               else
                {
-                  // Must create root directory on archive creation
-                  ArchiveDir dir = archive.OpenDirectory("", null, replace);
-                  if(dir)
+                  MakeDir(docDir);
+                  if(FileExists(docDir).isDirectory)
+                  {
                      readOnly = false;
-                  delete dir;
+                     RemoveDir(docDir);
+                  }
                }
-               delete archive;
             }
          }
 
@@ -2531,72 +2712,338 @@ class HelpView : HTMLView
 
    void SaveEdit()
    {
-      char archiveFile[MAX_LOCATION];
-      char fileName[MAX_FILENAME];
-      char directory[MAX_LOCATION];
-      const char * location;
-      Archive archive = null;
-      if(SplitArchivePath(editString, archiveFile, &location))
+      Block block;
+      bool empty = true;
+      String contents = null;
+      uint len;
+      TempFile f { };
+      for(block = textBlock.parent.subBlocks.first; block; block = block.next)
       {
-         GetLastDirectory(location, fileName);
-         StripLastDirectory(location, directory);
-         archive = ArchiveOpen(archiveFile, { true } );
+         if(block.type == TEXT && block.textLen)
+         {
+            empty = false;
+            break;
+         }
       }
+      if(!empty)
       {
-         TempFile f { };
-         Block block;
-         bool empty = true;
          for(block = textBlock.parent.subBlocks.first; block; block = block.next)
          {
-            if(block.type == TEXT && block.textLen)
-            {
-               empty = false;
-               break;
-            }
+            if(block.type == BR)
+               f.Puts("<br>");
+            else if(block.type == TEXT)
+               f.Write(block.text, 1, block.textLen);
          }
-         if(!empty)
+      }
+      f.Seek(0, start);
+      if((len = f.GetSize()))
+      {
+         contents = new char[len+1];
+         f.Read(contents, 1, len);
+         contents[len] = '\0';
+      }
+      if(!empty && contents)
+      {
+         char temp[MAX_LOCATION];
+         char part[MAX_FILENAME];
+         Module module;
+         void * object;
+         void * data;
+         DocumentationType type;
+         DocumentationItem item;
+         ItemDoc doc;
+         bool docRetrieved = false;
+         NamespaceDoc nsDoc = null;
+         ClassDoc clDoc = null;
+         FunctionDoc fnDoc = null;
+         MethodDoc mdDoc = null;
+         //char filePath[MAX_LOCATION];
+         Class cl = null;
+         Method method = null;
+         GlobalFunction function = null;
+
+         strcpy(temp, editString);
+         SplitDirectory(temp, part, temp);
+         module = (Module)strtoull(part, null, 16);
+         SplitDirectory(temp, part, temp);
+         object = (void *)strtoull(part, null, 16);
+         SplitDirectory(temp, part, temp);
+         data = (void *)strtoull(part, null, 16);
+         SplitDirectory(temp, part, temp);
+         if(!strcmp(part, "namespace"))
+            type = nameSpaceDoc;
+         else if(!strcmp(part, "function"))
+            type = functionDoc;
+         else if(!strcmp(part, "class"))
+            type = classDoc;
+         else if(!strcmp(part, "method"))
+            type = methodDoc;
+         SplitDirectory(temp, part, temp);
+         if(!strcmp(part, "description"))
+            item = description;
+         else if(!strcmp(part, "usage"))
+            item = usage;
+         else if(!strcmp(part, "remarks"))
+            item = remarks;
+         else if(!strcmp(part, "example"))
+            item = example;
+         else if(!strcmp(part, "seeAlso"))
+            item = seeAlso;
+         else if(!strcmp(part, "enumerationValue"))
+            item = enumerationValue;
+         else if(!strcmp(part, "definition"))
+            item = definition;
+         else if(!strcmp(part, "conversion"))
+            item = conversion;
+         else if(!strcmp(part, "memberDescription"))
+            item = memberDescription;
+         else if(!strcmp(part, "propertyDescription"))
+            item = propertyDescription;
+         else if(!strcmp(part, "parameter"))
+            item = parameter;
+         else if(!strcmp(part, "returnValue"))
+            item = returnValue;
+
+         doc = getDoc(temp, module, type, object, item, data, true);
+
          {
-            for(block = textBlock.parent.subBlocks.first; block; block = block.next)
+            MapIterator<const String, DocCacheEntry> it { map = docCache };
+            if(it.Index(temp, false))
             {
-               if(block.type == BR)
-                  f.Puts("<br>");
-               else if(block.type == TEXT)
-                  f.Write(block.text, 1, block.textLen);
+               it.data.doc = null;
+               delete it.data;
+               it.Remove();
             }
          }
-         f.Seek(0, start);
 
-         if(!empty || archive.FileExists(location))
+         switch(type)
          {
-            ArchiveDir dir = archive ? archive.OpenDirectory(directory, null, replace) : null;
-            if(dir)
-               dir.AddFromFile(fileName, f, null, replace, 0, null, null);
-            delete dir;
+            case classDoc:     cl = (Class)object; break;
+            case functionDoc:  function = object; break;
+            case methodDoc:    method = object; cl = method._class; break;
          }
-         delete archive;
-         delete f;
-         if(empty)
+
+         if(eClass_IsDerived(doc._class, class(ClassDoc)))
+         {
+            clDoc = (ClassDoc)doc;
+            docRetrieved = true;
+         }
+         else if(eClass_IsDerived(doc._class, class(NamespaceDoc)))
          {
-            Block parent = textBlock.parent;
-            while((block = parent.subBlocks.first))
+            nsDoc = (NamespaceDoc)doc;
+            docRetrieved = true;
+         }
+
+         if(docRetrieved)
+         {
+            if(type == functionDoc)
+            {
+               const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
+               if(name) name += 2; else name = function.name;
+               if(!nsDoc.functions) nsDoc.functions = { };
+               fnDoc = nsDoc.functions[name];
+               if(!fnDoc)
+               {
+                  fnDoc = { };
+                  nsDoc.functions[name] = fnDoc;
+               }
+            }
+            else if(type == methodDoc)
             {
-               parent.subBlocks.Remove(block);
-               delete block;
+               if(!clDoc.methods) clDoc.methods = { };
+               mdDoc = clDoc.methods[method.name];
+               if(!mdDoc)
+               {
+                  mdDoc = { };
+                  clDoc.methods[method.name] = mdDoc;
+               }
+            }
+
+            switch(item)
+            {
+               case description:
+                  if(type == methodDoc) { mdDoc.description = contents; contents = null; }
+                  else if(type == functionDoc) { fnDoc.description = contents; contents = null; }
+                  else if(type == classDoc) { clDoc.description = contents; contents = null; }
+                  else { nsDoc.description = contents; contents = null; }
+                  break;
+               case usage:
+                  if(type == methodDoc) { mdDoc.usage = contents; contents = null; }
+                  else if(type == functionDoc) { fnDoc.usage = contents; contents = null; }
+                  else if(type == classDoc) { clDoc.usage = contents; contents = null; }
+                  break;
+               case remarks:
+                  if(type == methodDoc) { mdDoc.remarks = contents; contents = null; }
+                  else if(type == functionDoc) { fnDoc.remarks = contents; contents = null; }
+                  else if(type == classDoc) { clDoc.remarks = contents; contents = null; }
+                  break;
+               case example:
+                  if(type == methodDoc) { mdDoc.example = contents; contents = null; }
+                  else if(type == functionDoc) { fnDoc.example = contents; contents = null; }
+                  else if(type == classDoc) { clDoc.example = contents; contents = null; }
+                  break;
+               case seeAlso:
+                  if(type == methodDoc) { mdDoc.also = contents; contents = null; }
+                  else if(type == functionDoc) { fnDoc.also = contents; contents = null; }
+                  else if(type == classDoc) { clDoc.also = contents; contents = null; }
+                  break;
+               case returnValue:
+                  if(type == methodDoc) { mdDoc.returnValue = contents; contents = null; }
+                  else if(type == functionDoc) { fnDoc.returnValue = contents; contents = null; }
+                  break;
+               case enumerationValue:
+               {
+                  ValueDoc itDoc;
+                  if(!clDoc.values) clDoc.values = { };
+                  itDoc = clDoc.values[((NamedLink)data).name];
+                  if(!itDoc)
+                  {
+                     itDoc = { };
+                     clDoc.values[((NamedLink)data).name] = itDoc;
+                  }
+                  itDoc.description = contents; contents = null;
+                  break;
+               }
+               case definition:
+               {
+                  DefineDoc itDoc;
+                  if(!nsDoc.defines) nsDoc.defines = { };
+                  itDoc = nsDoc.defines[((Definition)data).name];
+                  if(!itDoc)
+                  {
+                     itDoc = { };
+                     nsDoc.defines[((Definition)data).name] = itDoc;
+                  }
+                  itDoc.description = contents; contents = null;
+                  break;
+               }
+               case conversion:
+               {
+                  ConversionDoc itDoc;
+                  const char * name = RSearchString(((Property)data).name, "::", strlen(((Property)data).name), true, false);
+                  if(name) name += 2; else name = ((Property)data).name;
+                  if(!clDoc.conversions) clDoc.conversions = { };
+                  itDoc = clDoc.conversions[name];
+                  if(!itDoc)
+                  {
+                     itDoc = { };
+                     clDoc.conversions[name] = itDoc;
+                  }
+                  itDoc.description = contents; contents = null;
+                  break;
+               }
+               case memberDescription:
+               {
+                  FieldDoc itDoc;
+                  if(!clDoc.fields) clDoc.fields = { };
+                  itDoc = clDoc.fields[((DataMember)data).name];
+                  if(!itDoc)
+                  {
+                     itDoc = { };
+                     clDoc.fields[((DataMember)data).name] = itDoc;
+                  }
+                  itDoc.description = contents; contents = null;
+                  break;
+               }
+               case propertyDescription:
+               {
+                  PropertyDoc itDoc;
+                  if(!clDoc.properties) clDoc.properties = { };
+                  itDoc = clDoc.properties[((Property)data).name];
+                  if(!itDoc)
+                  {
+                     itDoc = { };
+                     clDoc.properties[((Property)data).name] = itDoc;
+                  }
+                  itDoc.description = contents; contents = null;
+                  break;
+               }
+               case parameter:
+               {
+                  ParameterDoc itDoc;
+                  char * name;
+                  Type prev;
+                  for(prev = data; prev; prev = prev.prev);
+                  name = ((Type)data).name;
+                  if(type == functionDoc)
+                  {
+                     if(!fnDoc.parameters) fnDoc.parameters = { };
+                     itDoc = fnDoc.parameters[name];
+                     if(!itDoc)
+                     {
+                        itDoc = { };
+                        fnDoc.parameters[name] = itDoc;
+                     }
+                     itDoc.description = contents; contents = null;
+                  }
+                  else if(type == methodDoc)
+                  {
+                     if(!mdDoc.parameters) mdDoc.parameters = { };
+                     itDoc = mdDoc.parameters[name];
+                     if(!itDoc)
+                     {
+                        itDoc = { };
+                        mdDoc.parameters[name] = itDoc;
+                     }
+                     itDoc.description = contents; contents = null;
+                  }
+                  break;
+               }
             }
-            textBlock = Block { type = TEXT, parent = parent, font = parent.font };
-            textBlock.text = CopyString($"[Add Text]");
-            textBlock.textLen = strlen(textBlock.text);
-            parent.subBlocks.Add(textBlock);
          }
 
-         edit = false;
-         if(created)
          {
-            ComputeMinSizes();
-            ComputeSizes();
-            PositionCaret(true);
-            Update(null);
+            File f;
+            char filePath[MAX_LOCATION];
+            char dirPath[MAX_LOCATION];
+            strcpy(filePath, temp);
+            StripLastDirectory(filePath, dirPath);
+            if(FileExists(filePath))
+               DeleteFile(filePath);
+            if(cl ? !clDoc.isEmpty : !nsDoc.isEmpty)
+            {
+               if(!FileExists(dirPath))
+                  MakeDir(dirPath);
+               f = FileOpen(filePath, write);
+               if(f)
+               {
+                  if(cl)
+                     WriteJSONObject(f, class(ClassDoc), clDoc, 0, true);
+                  else
+                     WriteJSONObject(f, class(NamespaceDoc), nsDoc, 0, true);
+                  delete f;
+               }
+               else
+               {
+                  PrintLn("error: writeClassDocFile -- problem opening file: ", filePath);
+               }
+            }
+         }
+         delete doc;
+         delete contents;
+      }
+
+      if(empty)
+      {
+         Block parent = textBlock.parent;
+         while((block = parent.subBlocks.first))
+         {
+            parent.subBlocks.Remove(block);
+            delete block;
          }
+         textBlock = Block { type = TEXT, parent = parent, font = parent.font };
+         textBlock.text = CopyString($"[Add Text]");
+         textBlock.textLen = strlen(textBlock.text);
+         parent.subBlocks.Add(textBlock);
+      }
+
+      edit = false;
+      if(created)
+      {
+         ComputeMinSizes();
+         ComputeSizes();
+         PositionCaret(true);
+         Update(null);
       }
    }
 
@@ -3990,3 +4437,196 @@ Thread commandThread
       return 0;
    }
 };
+#endif // !defined(EAR_TO_ECON_ECDOC)
+
+class ItemDoc
+{
+public:
+   property String name { get { return this ? name : null; } set { delete name; name = CopyString(value); } isset { return name && *name; } }
+   property String description { get { return this ? description : null; } set { delete description; description = CopyString(value); } isset { return description && *description; } }
+private:
+   char * name;
+   char * description;
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (name && *name) ||
+            (description && *description));
+      }
+   }
+   ~ItemDoc()
+   {
+      delete name;
+      delete description;
+   }
+}
+
+class MoreDoc : ItemDoc
+{
+public:
+   property String usage { get { return this ? usage : null; } set { delete usage; usage = CopyString(value); } isset { return usage && *usage; } }
+   property String example { get { return this ? example : null; } set { delete example; example = CopyString(value); } isset { return example && *example; } }
+   property String remarks { get { return this ? remarks : null; } set { delete remarks; remarks = CopyString(value); } isset { return remarks && *remarks; } }
+   property String also { get { return this ? also : null; } set { delete also; also = CopyString(value); } isset { return also && *also; } }
+private:
+   char * usage;
+   char * example;
+   char * remarks;
+   char * also;
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (usage && *usage) ||
+            (example && *example) ||
+            (remarks && *remarks) ||
+            (also && *also) ||
+            !ItemDoc::isEmpty);
+      }
+   }
+   ~MoreDoc()
+   {
+      delete usage;
+      delete example;
+      delete remarks;
+      delete also;
+   }
+}
+
+class NamespaceDoc : ItemDoc
+{
+public:
+   Map<String, DefineDoc> defines;
+   Map<String, FunctionDoc> functions;
+private:
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (defines && defines.count) ||
+            (functions && functions.count) ||
+            !ItemDoc::isEmpty);
+      }
+   }
+   ~NamespaceDoc()
+   {
+      delete defines;
+      delete functions;
+   }
+}
+
+class DefineDoc : ItemDoc { }
+
+class FunctionDoc : MoreDoc
+{
+public:
+   Map<String, ParameterDoc> parameters;
+   property String returnValue { get { return this ? returnValue : null; } set { delete returnValue; returnValue = CopyString(value); } isset { return returnValue && *returnValue; } }
+private:
+   char * returnValue;
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (parameters && parameters.count) ||
+            (returnValue && *returnValue) ||
+            !MoreDoc::isEmpty);
+      }
+   }
+   ~FunctionDoc()
+   {
+      delete parameters;
+      delete returnValue;
+   }
+}
+
+class ParameterDoc : ItemDoc
+{
+public:
+   uint position;
+}
+
+class ClassDoc : MoreDoc
+{
+public:
+   Map<String, ValueDoc> values;
+   Map<String, FieldDoc> fields;
+   Map<String, PropertyDoc> properties;
+   Map<String, ConversionDoc> conversions;
+   Map<String, MethodDoc> methods;
+private:
+   property bool isEmpty
+   {
+      get
+      {
+         return !(
+            (values && values.count) ||
+            (fields && fields.count) ||
+            (properties && properties.count) ||
+            (conversions && conversions.count) ||
+            (methods && methods.count) ||
+            !MoreDoc::isEmpty);
+      }
+   }
+   ~ClassDoc()
+   {
+      delete values;
+      delete fields;
+      delete properties;
+      delete conversions;
+      delete methods;
+   }
+}
+
+class ValueDoc : ItemDoc { }
+
+class FieldDoc : ItemDoc { }
+
+class PropertyDoc : ItemDoc { }
+
+class ConversionDoc : ItemDoc { }
+
+class MethodDoc : FunctionDoc { }
+
+char * getDocFileNameFromTypeName(const char * typeName)
+{
+   char * docFileName = new char[MAX_FILENAME];
+   const char * swap = "pointer";
+   const char * s = typeName;
+   char * d = docFileName;
+   const char * end = s + strlen(typeName);
+   int swapLen = strlen(swap);
+   for(; s < end; s++)
+   {
+      if(*s == ' ')
+         *d = '-';
+      else if(*s == '*')
+      {
+         strcpy(d, swap);
+         d += swapLen;
+      }
+      else
+         *d = *s;
+      d++;
+   }
+   *d = '\0';
+   return docFileName;
+}
+
+class DocCacheEntry
+{
+   Time added;
+   ItemDoc doc;
+
+   ~DocCacheEntry()
+   {
+      delete doc;
+   }
+}
+
+Map<const String, DocCacheEntry> docCache { };
diff --git a/documentor/tools/ear-to-econ-ecdoc.ec b/documentor/tools/ear-to-econ-ecdoc.ec
new file mode 100644 (file)
index 0000000..8921ea2
--- /dev/null
@@ -0,0 +1,733 @@
+import "ecere"
+import "ec"
+import "Documentor.ec"
+
+#define sflnprintln(...) PrintLn(__FILE__, ":", __LINE__, ": ", ##__VA_ARGS__)
+
+static Context globalContext { };
+static OldList defines { };
+static OldList imports { };
+static NameSpace globalData;
+static OldList excludedSymbols { offset = (uint)(uintptr)&((Symbol)0).left };
+
+define app = (Convertor)__thisModule.application;
+
+#define UTF8_NUM_BYTES(x)  (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
+
+default:
+/*extern */int __ecereVMethodID_class_OnGetString;
+private:
+
+
+static __attribute__((unused)) void Dummy()
+{
+   int a;
+   a.OnGetString(null, null, null);
+}
+
+static String ConvertReadDoc(const char * filePath)
+{
+   uint len;
+   String contents = null;
+   File f;
+   f = FileOpen(filePath, read);
+   if(f)
+   {
+      if((len = f.GetSize()))
+      {
+         contents = new char[len+1];
+         f.Read(contents, 1, len);
+         contents[len] = '\0';
+      }
+      delete f;
+   }
+   if(contents)
+   {
+      char * s;
+      for(s = contents; *s; s++)
+         if(!isspace(*s)) break;
+      if(!*s)
+         delete contents;
+   }
+   if(contents)
+   {
+      String buffer = new char[len+1];
+      char * i, * o = buffer;
+      for(i = contents; *i; i++)
+      {
+         if(i[0] == '<' && (i[1] == 'b' || i[1] == 'B') && (i[2] == 'r' || i[2] == 'R') && i[3] == '>' && i[4] == '\n')
+            i += 3;
+         else
+         {
+            *o = *i;
+            o++;
+         }
+      }
+      *o = 0;
+      delete contents;
+      contents = buffer;
+   }
+   return contents;
+}
+
+void ConvertModuleDoc(Module module, bool isDll)
+{
+   SubModule m;
+   bool readOnly = true;
+   char oldDocFilePath[MAX_LOCATION];
+   char docFilePath[MAX_LOCATION];
+   NameSpace * nameSpace = null;
+   if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
+      nameSpace = &module.application.systemNameSpace;
+   if(module.name && strcmp(module.name, "ecereCOM"))
+      nameSpace = &module.publicNameSpace;
+#ifdef _DEBUG
+   getDocFilePath(oldDocFilePath, null, module, nameSpace, false, true);
+   getDocFilePath(docFilePath, null, module, nameSpace, false, false);
+   PrintLn("ConvertModuleDoc:",
+         "  convertDocDir(", app.convertDocDir, ")",
+         "  outputDocDir(", app.outputDocDir, ")",
+         "  oldDocFile(", oldDocFilePath, ")",
+         "  docFile(", docFilePath, ")");
+#endif // def _DEBUG
+   getDocFilePath(oldDocFilePath, app.convertDocDir, module, nameSpace, true, true);
+   getDocFilePath(docFilePath, app.outputDocDir, module, nameSpace, true, false);
+
+   if(FileExists(oldDocFilePath))
+   {
+      if(FileExists(docFilePath).isDirectory)
+      {
+         char writeTestFilePath[MAX_LOCATION];
+         File f;
+         sprintf(writeTestFilePath, "%s/_", docFilePath);
+         f = FileOpen(writeTestFilePath, write);
+         PrintLn("Info: Directory exists for eCdoc in eCon format. Conversion is not required. Proceeding anyway.");
+         if(f)
+         {
+            delete f;
+            DeleteFile(writeTestFilePath);
+            readOnly = false;
+         }
+         else
+         {
+            readOnly = true;
+            PrintLn("Error: Directory for eCdoc in eCon format is not writable.");
+         }
+      }
+      else
+      {
+         MakeDir(docFilePath);
+         if(FileExists(docFilePath).isDirectory)
+         {
+            readOnly = false;
+         }
+         else
+         {
+            readOnly = true;
+            PrintLn("Error: Unable to create directory for eCdoc conversion to eCon.");
+         }
+      }
+
+      if(!readOnly)
+      {
+         char fileName[MAX_LOCATION];
+         DocConvertIterator fsi
+         {
+            bool onFolder(const char * folderPath, const char * folderName)
+            {
+               DocConvertLocType type = typeStack.lastIterator.data;
+               if(getNext)
+               {
+                  if(type == namespaces)
+                  {
+                     PathCatSlash(nsDir, folderName);
+                     namespaces.Add((ns = { namespaceDoc = { name = CopyString(folderName) }, nsPath = CopyString(nsDir) }));
+                     nsStack.Add(ns);
+                  }
+                  else if(type == functions)
+                  {
+                     functionDoc = FunctionDoc { };
+                     if(!ns.namespaceDoc.functions)
+                        ns.namespaceDoc.functions = { };
+                     ns.namespaceDoc.functions[folderName] = functionDoc;
+                  }
+                  else if(type == classes)
+                  {
+                     classDoc = ClassDoc { name = CopyString(folderName) };
+                     ns.classes.Add(classDoc);
+                  }
+                  else if(type == methods)
+                  {
+                     methodDoc = MethodDoc { };
+                     if(!classDoc.methods)
+                        classDoc.methods = { };
+                     classDoc.methods[folderName] = methodDoc;
+                  }
+                  else
+                     sflnprintln("what?");
+                  nameStack.Add(CopyString(folderName));
+                  getNext = false;
+                  if(!strcmp(folderName, "namespaces"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "functions"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "parameters"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "classes"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "enumeration values"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "data members"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "properties"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "conversions"))
+                     sflnprintln("what?");
+                  else if(!strcmp(folderName, "methods"))
+                     sflnprintln("what?");
+               }
+               else if((type == root || type == namespaces) && !strcmp(folderName, "namespaces"))
+                  addType(namespaces);
+               else if((type == root || type == namespaces) && !strcmp(folderName, "functions"))
+                  addType(functions);
+               else if((type == functions || type == methods) && !strcmp(folderName, "parameters"))
+                  addType(parameters);
+               else if((type == root || type == namespaces) && !strcmp(folderName, "classes"))
+                  addType(classes);
+               else if(type == classes && !strcmp(folderName, "enumeration values"))
+                  addType(values);
+               else if(type == classes && !strcmp(folderName, "data members"))
+                  addType(fields);
+               else if(type == classes && !strcmp(folderName, "properties"))
+                  addType(properties);
+               else if(type == classes && !strcmp(folderName, "conversions"))
+                  addType(conversions);
+               else if(type == classes && !strcmp(folderName, "methods"))
+                  addType(methods);
+               return true;
+            }
+
+            void outFolder(const char * folderPath, const char * folderName, bool isRoot)
+            {
+               DocConvertLocType type = typeStack.lastIterator.data;
+               if(!isRoot)
+               {
+                  if(!strcmp(folderName, "namespaces"))
+                     exitType();
+                  else if(!strcmp(folderName, "functions"))
+                     exitType();
+                  else if(!strcmp(folderName, "parameters"))
+                     exitType();
+                  else if(!strcmp(folderName, "classes"))
+                     exitType();
+                  else if(!strcmp(folderName, "enumeration values"))
+                     exitType();
+                  else if(!strcmp(folderName, "data members"))
+                     exitType();
+                  else if(!strcmp(folderName, "properties"))
+                     exitType();
+                  else if(!strcmp(folderName, "conversions"))
+                     exitType();
+                  else if(!strcmp(folderName, "methods"))
+                     exitType();
+                  else if(!strcmp(folderName, nameStack.lastIterator.data))
+                  {
+                     if(type == namespaces)
+                     {
+                        StripLastDirectory(nsDir, nsDir);
+                        nsStack.Remove(nsStack.lastIterator.pointer);
+                        ns = nsStack.lastIterator.data;
+                     }
+                     delete nameStack.lastIterator.data;
+                     nameStack.Remove(nameStack.lastIterator.pointer);
+                     getNext = true;
+                  }
+               }
+            }
+
+            bool onFile(const char * filePath, const char * fileName)
+            {
+               DocConvertLocType type = typeStack.lastIterator.data;
+               if(!strstr(fileName, ".eCdoc"))
+               {
+                  String doc = ConvertReadDoc(filePath);
+                  if((type == root || type == namespaces || type == classes || type == methods || type == functions) && !strcmp(fileName, "description"))
+                  {
+                     if(type == root || type == namespaces) { ns.namespaceDoc.description = doc; doc = null; }
+                     else if(type == classes) { classDoc.description = doc; doc = null; }
+                     else if(type == methods) { methodDoc.description = doc; doc = null; }
+                     else if(type == functions) { functionDoc.description = doc; doc = null; }
+                  }
+                  else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "usage"))
+                  {
+                     if(type == classes) { classDoc.usage = doc; doc = null; }
+                     else if(type == methods) { methodDoc.usage = doc; doc = null; }
+                     else if(type == functions) { functionDoc.usage = doc; doc = null; }
+                  }
+                  else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "example"))
+                  {
+                     if(type == classes) { classDoc.example = doc; doc = null; }
+                     else if(type == methods) { methodDoc.example = doc; doc = null; }
+                     else if(type == functions) { functionDoc.example = doc; doc = null; }
+                  }
+                  else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "remarks"))
+                  {
+                     if(type == classes) { classDoc.remarks = doc; doc = null; }
+                     else if(type == methods) { methodDoc.remarks = doc; doc = null; }
+                     else if(type == functions) { functionDoc.remarks = doc; doc = null; }
+                  }
+                  else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "seeAlso"))
+                  {
+                     if(type == classes) { classDoc.also = doc; doc = null; }
+                     else if(type == methods) { methodDoc.also = doc; doc = null; }
+                     else if(type == functions) { functionDoc.also = doc; doc = null; }
+                  }
+                  else if((type == methods || type == functions) && !strcmp(fileName, "returnValue"))
+                  {
+                     if(type == methods) { methodDoc.returnValue = doc; doc = null; }
+                     else if(type == functions) { functionDoc.returnValue = doc; doc = null; }
+                  }
+                  else
+                  {
+                     if(type == parameters)
+                     {
+                        uint pos;
+                        char * s;
+                        char name[MAX_FILENAME];
+                        ParameterDoc parameterDoc;
+                        DocConvertLocType parentType = typeStack[typeStack.count-2];
+                        strcpy(name, fileName);
+                        s = strstr(name, ".");
+                        if(s)
+                        {
+                           *s = 0;
+                           s++;
+                           pos = atoi(s);
+                        }
+                        else
+                           sflnprintln("what?");
+                        if(parentType == functions)
+                        {
+                           parameterDoc = ParameterDoc { description = doc, position = pos };
+                           doc = null;
+                           if(!functionDoc.parameters)
+                              functionDoc.parameters = { };
+                           functionDoc.parameters[name] = parameterDoc;
+                        }
+                        else if(parentType == methods)
+                        {
+                           parameterDoc = ParameterDoc { description = doc, position = pos };
+                           doc = null;
+                           if(!methodDoc.parameters)
+                              methodDoc.parameters = { };
+                           methodDoc.parameters[name] = parameterDoc;
+                        }
+                        else
+                           sflnprintln("what?");
+                     }
+                     else if(type == values)
+                     {
+                        ValueDoc valueDoc { description = doc };
+                        doc = null;
+                        if(!classDoc.values)
+                           classDoc.values = { };
+                        classDoc.values[fileName] = valueDoc;
+                     }
+                     else if(type == fields)
+                     {
+                        FieldDoc fieldDoc { description = doc };
+                        doc = null;
+                        if(!classDoc.fields)
+                           classDoc.fields = { };
+                        classDoc.fields[fileName] = fieldDoc;
+                     }
+                     else if(type == properties)
+                     {
+                        PropertyDoc propertyDoc { description = doc };
+                        doc = null;
+                        if(!classDoc.properties)
+                           classDoc.properties = { };
+                       classDoc.properties[fileName] = propertyDoc;
+                     }
+                     else if(type == conversions)
+                     {
+                        ConversionDoc conversionDoc { description = doc };
+                        doc = null;
+                        if(!classDoc.conversions)
+                           classDoc.conversions = { };
+                        classDoc.conversions[fileName] = conversionDoc;
+                     }
+                     else
+                        sflnprintln("what?");
+                  }
+                  delete doc;
+               }
+               return true;
+            }
+         };
+         fsi.nsDir[0] = '\0';
+         PathCatSlash(fsi.nsDir, docFilePath);
+         fsi.typeStack.Add(root);
+         fsi.namespaces.Add((fsi.ns = { namespaceDoc = { }, nsPath = CopyString(fsi.nsDir) }));
+         fsi.nsStack.Add(fsi.ns);
+         sprintf(fileName, "<%s>", oldDocFilePath);
+         fsi.iterate(fileName);
+         for(ns : fsi.namespaces)
+         {
+            writeNamespaceDocFile(ns.namespaceDoc, ns.nsPath);
+            for(c : ns.classes)
+            {
+               writeClassDocFile(c, ns.nsPath);
+            }
+         }
+      }
+   }
+   else
+   {
+      PrintLn("Info: old eCdoc format file (", oldDocFilePath, ") does not exist.");
+   }
+
+   for(m = module.modules.first; m; m = m.next)
+   {
+      if(m.importMode == publicAccess || !isDll)
+         ConvertModuleDoc(m.module, true);
+   }
+}
+
+void getDocFilePath(char * docFilePath, char * docDir, Module module, NameSpace * ns, bool includeDir, bool old)
+{
+   sprintf(docFilePath, old ? "%s%s%s.eCdoc" : "%s%s%s", includeDir ? docDir : "", includeDir ? "/" : "",
+         (!module || !module.name ||
+               (ns && ns->name && !strcmp(ns->name, "namespaces/ecere/namespaces/com"))) ? "ecereCOM" : module.name);
+}
+
+static void writeNamespaceDocFile(NamespaceDoc namespaceDoc, const char * path)
+{
+   if(!namespaceDoc.isEmpty)
+   {
+      char * filePath = new char[MAX_LOCATION];
+      File f;
+      strcpy(filePath, path);
+      PathCatSlash(filePath, "_global-defs");
+      ChangeExtension(filePath, "econ", filePath);
+      MakeDir(path);
+      DeleteFile(filePath);
+      f = FileOpen(filePath, write);
+      if(f)
+      {
+         WriteJSONObject(f, class(NamespaceDoc), namespaceDoc, 0, true);
+         delete f;
+      }
+      else
+      {
+         PrintLn("error: writeNamespaceDocFile -- problem opening file: ", filePath);
+      }
+      delete filePath;
+   }
+}
+
+static void writeClassDocFile(ClassDoc classDoc, const char * path)
+{
+   if(!classDoc.isEmpty)
+   {
+      char * name = getDocFileNameFromTypeName(classDoc.name);
+      char * filePath = new char[MAX_LOCATION];
+      File f;
+      strcpy(filePath, path);
+      PathCatSlash(filePath, name);
+      ChangeExtension(filePath, "econ", filePath);
+      DeleteFile(filePath);
+      f = FileOpen(filePath, write);
+      if(f)
+      {
+         WriteJSONObject(f, class(ClassDoc), classDoc, 0, true);
+         delete f;
+      }
+      else
+      {
+         PrintLn("error: writeClassDocFile -- problem opening file: ", filePath);
+      }
+      delete name;
+      delete filePath;
+   }
+}
+
+Application componentsApp;
+
+class Convertor : Application
+{
+   char * moduleName;;
+   char * convertDocDir;
+   char * outputDocDir;
+
+   bool Init()
+   {
+      SetGlobalContext(globalContext);
+      SetExcludedSymbols(&excludedSymbols);
+      SetDefines(&::defines);
+      SetImports(&imports);
+      SetInDocumentor(true);
+
+      SetGlobalData(globalData);
+
+      if(argc == 3 || argc == 4)
+      {
+         convertDocDir = CopyString(argv[1]);
+         outputDocDir = CopyString(argv[2]);
+         if(argc == 4)
+            moduleName = CopyString(argv[3]);
+         else
+            moduleName = CopyString("ecere");
+         return true;
+      }
+      return false;
+   }
+
+   void Main()
+   {
+      if(Init())
+      {
+         OpenModule(moduleName);
+      }
+      else
+      {
+         PrintLn($"Syntax: exename <old doc dir> <new doc dir> [<module name>]");
+      }
+      system("pause");
+   }
+
+   void Terminate()
+   {
+      delete moduleName;
+      delete convertDocDir;
+      delete outputDocDir;
+
+      FreeContext(globalContext);
+      FreeExcludedSymbols(excludedSymbols);
+      ::defines.Free(FreeModuleDefine);
+      imports.Free(FreeModuleImport);
+
+      FreeGlobalData(globalData);
+      FreeTypeData(componentsApp);
+      FreeIncludeFiles();
+      delete componentsApp;
+   }
+
+   void OpenModule(const char * filePath)
+   {
+      char moduleName[MAX_LOCATION];
+      char extension[MAX_EXTENSION];
+      Module module = null;
+      static char symbolsDir[MAX_LOCATION];
+
+      FreeContext(globalContext);
+      FreeExcludedSymbols(excludedSymbols);
+      ::defines.Free(FreeModuleDefine);
+      imports.Free(FreeModuleImport);
+
+      FreeGlobalData(globalData);
+      FreeIncludeFiles();
+      if(componentsApp)
+      {
+         FreeTypeData(componentsApp);
+         delete componentsApp;
+      }
+
+      componentsApp = __ecere_COM_Initialize(false, 1, null);
+      SetPrivateModule(componentsApp);
+
+      StripLastDirectory(filePath, symbolsDir);
+      SetSymbolsDir(symbolsDir);
+
+      GetExtension(filePath, extension);
+
+      ImportModule(filePath, normalImport, publicAccess, false);
+
+      if(extension[0] && strcmpi(extension, "so") && strcmpi(extension, "dll") && strcmpi(extension, "dylib"))
+         componentsApp.name = CopyString(filePath);
+
+      for(module = componentsApp.allModules.first; module; module = module.next)
+      {
+         if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
+            break;
+      }
+      if(!module)
+         eModule_LoadStrict(componentsApp, "ecereCOM", publicAccess /*privateAccess*/);
+
+      GetLastDirectory(filePath, moduleName);
+      // Extension, path and lib prefix get removed in Module::name
+      if(extension[0])
+      {
+         StripExtension(moduleName);
+         if((!strcmpi(extension, "so") || !strcmpi(extension, "dylib")) && strstr(moduleName, "lib") == moduleName)
+         {
+            int len = strlen(moduleName) - 3;
+            memmove(moduleName, moduleName + 3, len);
+            moduleName[len] = 0;
+         }
+      }
+
+      ConvertModuleDoc(componentsApp, false);
+   }
+}
+
+class DocConvertNamespaceStackFrame
+{
+   NamespaceDoc namespaceDoc { };
+   Array<ClassDoc> classes { };
+   char * nsPath;
+}
+
+enum DocConvertLocType { root, namespaces, functions, parameters, classes, values, fields, properties, conversions, methods };
+
+class DocConvertIterator : NormalFileSystemIterator
+{
+   char * nsDir;
+
+   bool getNext;
+   Array<String> nameStack { };
+   Array<DocConvertLocType> typeStack { };
+   Array<DocConvertNamespaceStackFrame> nsStack { };
+   Array<DocConvertNamespaceStackFrame> namespaces { };
+   DocConvertNamespaceStackFrame ns;
+   DefineDoc defineDoc;
+   FunctionDoc functionDoc;
+   ClassDoc classDoc;
+   MethodDoc methodDoc;
+
+   void addType(DocConvertLocType type)
+   {
+      typeStack.Add(type);
+      getNext = true;
+   }
+   void exitType()
+   {
+      typeStack.Remove(typeStack.lastIterator.pointer);
+      getNext = false;
+   }
+
+   DocConvertIterator()
+   {
+      nsDir = new char[MAX_LOCATION];
+   }
+   ~DocConvertIterator()
+   {
+      delete nsDir;
+   }
+}
+
+public class NormalFileSystemIterator : FileSystemIterator
+{
+public:
+   Array<StackFrame> stack { };
+
+   char * extensions;
+   property char * extensions { set { delete extensions; if(value) extensions = CopyString(value); } }
+
+   ~NormalFileSystemIterator()
+   {
+      delete extensions;
+   }
+
+   void iterate(const char * startPath)
+   {
+      StackFrame frame;
+      char startName[MAX_FILENAME];
+      startName[0] = '\0';
+      GetLastDirectory(startPath, startName);
+
+      onInit(startPath, startName);
+      {
+         frame = StackFrame { };
+         stack.Add(frame);
+         frame.path = CopyString(startPath);
+         frame.listing = FileListing { startPath, extensions = extensions };
+      }
+
+      if(iterateStartPath)
+      {
+         FileAttribs attribs = FileExists(startPath);
+         if(attribs.isDrive)
+            onVolume(startPath);
+         else
+         {
+            if(attribs.isDirectory)
+               onFolder(startPath, startName);
+            else if(attribs.isFile)
+               onFile(startPath, startName);
+         }
+      }
+
+      while(stack.count)
+      {
+         if(frame.listing.Find())
+         {
+            bool peek = frame.listing.stats.attribs.isDirectory && onFolder(frame.listing.path, frame.listing.name);
+            if(!frame.listing.stats.attribs.isDirectory)
+            {
+               onFile(frame.listing.path, frame.listing.name);
+            }
+            else if(peek)
+            {
+               StackFrame newFrame { };
+               stack.Add(newFrame);
+               newFrame.path = CopyString(frame.listing.path);
+               newFrame.listing = FileListing { newFrame.path, extensions = frame.listing.extensions };
+               frame = newFrame;
+            }
+         }
+         else
+         {
+            StackFrame parentFrame = stack.count > 1 ? stack[stack.count - 2] : null;
+            outFolder(parentFrame ? parentFrame.listing.path : startPath, parentFrame ? parentFrame.listing.name : startName, !parentFrame);
+            stack.lastIterator.Remove();
+            if(stack.count)
+               frame = stack.lastIterator.data;
+            else
+               frame = null;
+         }
+      }
+   }
+}
+
+public class FileSystemIterator
+{
+public:
+   bool iterateStartPath;
+
+   virtual bool onInit(const char * startPath, const char * startName)
+   {
+      return false;
+   }
+
+   virtual bool onFile(const char * filePath, const char * fileName)
+   {
+      return true;
+   }
+
+   virtual bool onFolder(const char * folderPath, const char * folderName)
+   {
+      return true;
+   }
+
+   virtual bool onVolume(const char * volumePath)
+   {
+      return true;
+   }
+
+   virtual void outFolder(const char * folderPath, const char * folderName, bool isRoot)
+   {
+   }
+}
+
+public class StackFrame
+{
+   int tag;
+   char * path;
+   FileListing listing;
+
+   ~StackFrame()
+   {
+      delete path;
+   }
+};
diff --git a/documentor/tools/ear-to-econ-ecdoc.epj b/documentor/tools/ear-to-econ-ecdoc.epj
new file mode 100644 (file)
index 0000000..3f523d1
--- /dev/null
@@ -0,0 +1,45 @@
+{
+   "Version" : 0.2,
+   "ModuleName" : "ear-to-econ-ecdoc",
+   "Options" : {
+      "Warnings" : "All",
+      "PreprocessorDefinitions" : [
+         "EAR_TO_ECON_ECDOC"
+      ],
+      "TargetType" : "Executable",
+      "TargetFileName" : "ear-to-econ-ecdoc",
+      "Libraries" : [
+         "ecere"
+      ],
+      "Console" : true
+   },
+   "Configurations" : [
+      {
+         "Name" : "Debug",
+         "Options" : {
+            "Debug" : true,
+            "Optimization" : "None",
+            "PreprocessorDefinitions" : [
+               "_DEBUG"
+            ],
+            "FastMath" : false
+         }
+      },
+      {
+         "Name" : "Release",
+         "Options" : {
+            "Debug" : false,
+            "Optimization" : "Speed",
+            "FastMath" : true
+         }
+      }
+   ],
+   "Files" : [
+      "ear-to-econ-ecdoc.ec",
+      "../src/Documentor.ec"
+   ],
+   "ResourcesPath" : "",
+   "Resources" : [
+
+   ]
+}