8921ea2daf6c0588847fce2aa0a744c4320591d6
[sdk] / documentor / tools / ear-to-econ-ecdoc.ec
1 import "ecere"
2 import "ec"
3 import "Documentor.ec"
4
5 #define sflnprintln(...) PrintLn(__FILE__, ":", __LINE__, ": ", ##__VA_ARGS__)
6
7 static Context globalContext { };
8 static OldList defines { };
9 static OldList imports { };
10 static NameSpace globalData;
11 static OldList excludedSymbols { offset = (uint)(uintptr)&((Symbol)0).left };
12
13 define app = (Convertor)__thisModule.application;
14
15 #define UTF8_NUM_BYTES(x)  (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
16
17 default:
18 /*extern */int __ecereVMethodID_class_OnGetString;
19 private:
20
21
22 static __attribute__((unused)) void Dummy()
23 {
24    int a;
25    a.OnGetString(null, null, null);
26 }
27
28 static String ConvertReadDoc(const char * filePath)
29 {
30    uint len;
31    String contents = null;
32    File f;
33    f = FileOpen(filePath, read);
34    if(f)
35    {
36       if((len = f.GetSize()))
37       {
38          contents = new char[len+1];
39          f.Read(contents, 1, len);
40          contents[len] = '\0';
41       }
42       delete f;
43    }
44    if(contents)
45    {
46       char * s;
47       for(s = contents; *s; s++)
48          if(!isspace(*s)) break;
49       if(!*s)
50          delete contents;
51    }
52    if(contents)
53    {
54       String buffer = new char[len+1];
55       char * i, * o = buffer;
56       for(i = contents; *i; i++)
57       {
58          if(i[0] == '<' && (i[1] == 'b' || i[1] == 'B') && (i[2] == 'r' || i[2] == 'R') && i[3] == '>' && i[4] == '\n')
59             i += 3;
60          else
61          {
62             *o = *i;
63             o++;
64          }
65       }
66       *o = 0;
67       delete contents;
68       contents = buffer;
69    }
70    return contents;
71 }
72
73 void ConvertModuleDoc(Module module, bool isDll)
74 {
75    SubModule m;
76    bool readOnly = true;
77    char oldDocFilePath[MAX_LOCATION];
78    char docFilePath[MAX_LOCATION];
79    NameSpace * nameSpace = null;
80    if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
81       nameSpace = &module.application.systemNameSpace;
82    if(module.name && strcmp(module.name, "ecereCOM"))
83       nameSpace = &module.publicNameSpace;
84 #ifdef _DEBUG
85    getDocFilePath(oldDocFilePath, null, module, nameSpace, false, true);
86    getDocFilePath(docFilePath, null, module, nameSpace, false, false);
87    PrintLn("ConvertModuleDoc:",
88          "  convertDocDir(", app.convertDocDir, ")",
89          "  outputDocDir(", app.outputDocDir, ")",
90          "  oldDocFile(", oldDocFilePath, ")",
91          "  docFile(", docFilePath, ")");
92 #endif // def _DEBUG
93    getDocFilePath(oldDocFilePath, app.convertDocDir, module, nameSpace, true, true);
94    getDocFilePath(docFilePath, app.outputDocDir, module, nameSpace, true, false);
95
96    if(FileExists(oldDocFilePath))
97    {
98       if(FileExists(docFilePath).isDirectory)
99       {
100          char writeTestFilePath[MAX_LOCATION];
101          File f;
102          sprintf(writeTestFilePath, "%s/_", docFilePath);
103          f = FileOpen(writeTestFilePath, write);
104          PrintLn("Info: Directory exists for eCdoc in eCon format. Conversion is not required. Proceeding anyway.");
105          if(f)
106          {
107             delete f;
108             DeleteFile(writeTestFilePath);
109             readOnly = false;
110          }
111          else
112          {
113             readOnly = true;
114             PrintLn("Error: Directory for eCdoc in eCon format is not writable.");
115          }
116       }
117       else
118       {
119          MakeDir(docFilePath);
120          if(FileExists(docFilePath).isDirectory)
121          {
122             readOnly = false;
123          }
124          else
125          {
126             readOnly = true;
127             PrintLn("Error: Unable to create directory for eCdoc conversion to eCon.");
128          }
129       }
130
131       if(!readOnly)
132       {
133          char fileName[MAX_LOCATION];
134          DocConvertIterator fsi
135          {
136             bool onFolder(const char * folderPath, const char * folderName)
137             {
138                DocConvertLocType type = typeStack.lastIterator.data;
139                if(getNext)
140                {
141                   if(type == namespaces)
142                   {
143                      PathCatSlash(nsDir, folderName);
144                      namespaces.Add((ns = { namespaceDoc = { name = CopyString(folderName) }, nsPath = CopyString(nsDir) }));
145                      nsStack.Add(ns);
146                   }
147                   else if(type == functions)
148                   {
149                      functionDoc = FunctionDoc { };
150                      if(!ns.namespaceDoc.functions)
151                         ns.namespaceDoc.functions = { };
152                      ns.namespaceDoc.functions[folderName] = functionDoc;
153                   }
154                   else if(type == classes)
155                   {
156                      classDoc = ClassDoc { name = CopyString(folderName) };
157                      ns.classes.Add(classDoc);
158                   }
159                   else if(type == methods)
160                   {
161                      methodDoc = MethodDoc { };
162                      if(!classDoc.methods)
163                         classDoc.methods = { };
164                      classDoc.methods[folderName] = methodDoc;
165                   }
166                   else
167                      sflnprintln("what?");
168                   nameStack.Add(CopyString(folderName));
169                   getNext = false;
170                   if(!strcmp(folderName, "namespaces"))
171                      sflnprintln("what?");
172                   else if(!strcmp(folderName, "functions"))
173                      sflnprintln("what?");
174                   else if(!strcmp(folderName, "parameters"))
175                      sflnprintln("what?");
176                   else if(!strcmp(folderName, "classes"))
177                      sflnprintln("what?");
178                   else if(!strcmp(folderName, "enumeration values"))
179                      sflnprintln("what?");
180                   else if(!strcmp(folderName, "data members"))
181                      sflnprintln("what?");
182                   else if(!strcmp(folderName, "properties"))
183                      sflnprintln("what?");
184                   else if(!strcmp(folderName, "conversions"))
185                      sflnprintln("what?");
186                   else if(!strcmp(folderName, "methods"))
187                      sflnprintln("what?");
188                }
189                else if((type == root || type == namespaces) && !strcmp(folderName, "namespaces"))
190                   addType(namespaces);
191                else if((type == root || type == namespaces) && !strcmp(folderName, "functions"))
192                   addType(functions);
193                else if((type == functions || type == methods) && !strcmp(folderName, "parameters"))
194                   addType(parameters);
195                else if((type == root || type == namespaces) && !strcmp(folderName, "classes"))
196                   addType(classes);
197                else if(type == classes && !strcmp(folderName, "enumeration values"))
198                   addType(values);
199                else if(type == classes && !strcmp(folderName, "data members"))
200                   addType(fields);
201                else if(type == classes && !strcmp(folderName, "properties"))
202                   addType(properties);
203                else if(type == classes && !strcmp(folderName, "conversions"))
204                   addType(conversions);
205                else if(type == classes && !strcmp(folderName, "methods"))
206                   addType(methods);
207                return true;
208             }
209
210             void outFolder(const char * folderPath, const char * folderName, bool isRoot)
211             {
212                DocConvertLocType type = typeStack.lastIterator.data;
213                if(!isRoot)
214                {
215                   if(!strcmp(folderName, "namespaces"))
216                      exitType();
217                   else if(!strcmp(folderName, "functions"))
218                      exitType();
219                   else if(!strcmp(folderName, "parameters"))
220                      exitType();
221                   else if(!strcmp(folderName, "classes"))
222                      exitType();
223                   else if(!strcmp(folderName, "enumeration values"))
224                      exitType();
225                   else if(!strcmp(folderName, "data members"))
226                      exitType();
227                   else if(!strcmp(folderName, "properties"))
228                      exitType();
229                   else if(!strcmp(folderName, "conversions"))
230                      exitType();
231                   else if(!strcmp(folderName, "methods"))
232                      exitType();
233                   else if(!strcmp(folderName, nameStack.lastIterator.data))
234                   {
235                      if(type == namespaces)
236                      {
237                         StripLastDirectory(nsDir, nsDir);
238                         nsStack.Remove(nsStack.lastIterator.pointer);
239                         ns = nsStack.lastIterator.data;
240                      }
241                      delete nameStack.lastIterator.data;
242                      nameStack.Remove(nameStack.lastIterator.pointer);
243                      getNext = true;
244                   }
245                }
246             }
247
248             bool onFile(const char * filePath, const char * fileName)
249             {
250                DocConvertLocType type = typeStack.lastIterator.data;
251                if(!strstr(fileName, ".eCdoc"))
252                {
253                   String doc = ConvertReadDoc(filePath);
254                   if((type == root || type == namespaces || type == classes || type == methods || type == functions) && !strcmp(fileName, "description"))
255                   {
256                      if(type == root || type == namespaces) { ns.namespaceDoc.description = doc; doc = null; }
257                      else if(type == classes) { classDoc.description = doc; doc = null; }
258                      else if(type == methods) { methodDoc.description = doc; doc = null; }
259                      else if(type == functions) { functionDoc.description = doc; doc = null; }
260                   }
261                   else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "usage"))
262                   {
263                      if(type == classes) { classDoc.usage = doc; doc = null; }
264                      else if(type == methods) { methodDoc.usage = doc; doc = null; }
265                      else if(type == functions) { functionDoc.usage = doc; doc = null; }
266                   }
267                   else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "example"))
268                   {
269                      if(type == classes) { classDoc.example = doc; doc = null; }
270                      else if(type == methods) { methodDoc.example = doc; doc = null; }
271                      else if(type == functions) { functionDoc.example = doc; doc = null; }
272                   }
273                   else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "remarks"))
274                   {
275                      if(type == classes) { classDoc.remarks = doc; doc = null; }
276                      else if(type == methods) { methodDoc.remarks = doc; doc = null; }
277                      else if(type == functions) { functionDoc.remarks = doc; doc = null; }
278                   }
279                   else if((type == classes || type == methods || type == functions) && !strcmp(fileName, "seeAlso"))
280                   {
281                      if(type == classes) { classDoc.also = doc; doc = null; }
282                      else if(type == methods) { methodDoc.also = doc; doc = null; }
283                      else if(type == functions) { functionDoc.also = doc; doc = null; }
284                   }
285                   else if((type == methods || type == functions) && !strcmp(fileName, "returnValue"))
286                   {
287                      if(type == methods) { methodDoc.returnValue = doc; doc = null; }
288                      else if(type == functions) { functionDoc.returnValue = doc; doc = null; }
289                   }
290                   else
291                   {
292                      if(type == parameters)
293                      {
294                         uint pos;
295                         char * s;
296                         char name[MAX_FILENAME];
297                         ParameterDoc parameterDoc;
298                         DocConvertLocType parentType = typeStack[typeStack.count-2];
299                         strcpy(name, fileName);
300                         s = strstr(name, ".");
301                         if(s)
302                         {
303                            *s = 0;
304                            s++;
305                            pos = atoi(s);
306                         }
307                         else
308                            sflnprintln("what?");
309                         if(parentType == functions)
310                         {
311                            parameterDoc = ParameterDoc { description = doc, position = pos };
312                            doc = null;
313                            if(!functionDoc.parameters)
314                               functionDoc.parameters = { };
315                            functionDoc.parameters[name] = parameterDoc;
316                         }
317                         else if(parentType == methods)
318                         {
319                            parameterDoc = ParameterDoc { description = doc, position = pos };
320                            doc = null;
321                            if(!methodDoc.parameters)
322                               methodDoc.parameters = { };
323                            methodDoc.parameters[name] = parameterDoc;
324                         }
325                         else
326                            sflnprintln("what?");
327                      }
328                      else if(type == values)
329                      {
330                         ValueDoc valueDoc { description = doc };
331                         doc = null;
332                         if(!classDoc.values)
333                            classDoc.values = { };
334                         classDoc.values[fileName] = valueDoc;
335                      }
336                      else if(type == fields)
337                      {
338                         FieldDoc fieldDoc { description = doc };
339                         doc = null;
340                         if(!classDoc.fields)
341                            classDoc.fields = { };
342                         classDoc.fields[fileName] = fieldDoc;
343                      }
344                      else if(type == properties)
345                      {
346                         PropertyDoc propertyDoc { description = doc };
347                         doc = null;
348                         if(!classDoc.properties)
349                            classDoc.properties = { };
350                        classDoc.properties[fileName] = propertyDoc;
351                      }
352                      else if(type == conversions)
353                      {
354                         ConversionDoc conversionDoc { description = doc };
355                         doc = null;
356                         if(!classDoc.conversions)
357                            classDoc.conversions = { };
358                         classDoc.conversions[fileName] = conversionDoc;
359                      }
360                      else
361                         sflnprintln("what?");
362                   }
363                   delete doc;
364                }
365                return true;
366             }
367          };
368          fsi.nsDir[0] = '\0';
369          PathCatSlash(fsi.nsDir, docFilePath);
370          fsi.typeStack.Add(root);
371          fsi.namespaces.Add((fsi.ns = { namespaceDoc = { }, nsPath = CopyString(fsi.nsDir) }));
372          fsi.nsStack.Add(fsi.ns);
373          sprintf(fileName, "<%s>", oldDocFilePath);
374          fsi.iterate(fileName);
375          for(ns : fsi.namespaces)
376          {
377             writeNamespaceDocFile(ns.namespaceDoc, ns.nsPath);
378             for(c : ns.classes)
379             {
380                writeClassDocFile(c, ns.nsPath);
381             }
382          }
383       }
384    }
385    else
386    {
387       PrintLn("Info: old eCdoc format file (", oldDocFilePath, ") does not exist.");
388    }
389
390    for(m = module.modules.first; m; m = m.next)
391    {
392       if(m.importMode == publicAccess || !isDll)
393          ConvertModuleDoc(m.module, true);
394    }
395 }
396
397 void getDocFilePath(char * docFilePath, char * docDir, Module module, NameSpace * ns, bool includeDir, bool old)
398 {
399    sprintf(docFilePath, old ? "%s%s%s.eCdoc" : "%s%s%s", includeDir ? docDir : "", includeDir ? "/" : "",
400          (!module || !module.name ||
401                (ns && ns->name && !strcmp(ns->name, "namespaces/ecere/namespaces/com"))) ? "ecereCOM" : module.name);
402 }
403
404 static void writeNamespaceDocFile(NamespaceDoc namespaceDoc, const char * path)
405 {
406    if(!namespaceDoc.isEmpty)
407    {
408       char * filePath = new char[MAX_LOCATION];
409       File f;
410       strcpy(filePath, path);
411       PathCatSlash(filePath, "_global-defs");
412       ChangeExtension(filePath, "econ", filePath);
413       MakeDir(path);
414       DeleteFile(filePath);
415       f = FileOpen(filePath, write);
416       if(f)
417       {
418          WriteJSONObject(f, class(NamespaceDoc), namespaceDoc, 0, true);
419          delete f;
420       }
421       else
422       {
423          PrintLn("error: writeNamespaceDocFile -- problem opening file: ", filePath);
424       }
425       delete filePath;
426    }
427 }
428
429 static void writeClassDocFile(ClassDoc classDoc, const char * path)
430 {
431    if(!classDoc.isEmpty)
432    {
433       char * name = getDocFileNameFromTypeName(classDoc.name);
434       char * filePath = new char[MAX_LOCATION];
435       File f;
436       strcpy(filePath, path);
437       PathCatSlash(filePath, name);
438       ChangeExtension(filePath, "econ", filePath);
439       DeleteFile(filePath);
440       f = FileOpen(filePath, write);
441       if(f)
442       {
443          WriteJSONObject(f, class(ClassDoc), classDoc, 0, true);
444          delete f;
445       }
446       else
447       {
448          PrintLn("error: writeClassDocFile -- problem opening file: ", filePath);
449       }
450       delete name;
451       delete filePath;
452    }
453 }
454
455 Application componentsApp;
456
457 class Convertor : Application
458 {
459    char * moduleName;;
460    char * convertDocDir;
461    char * outputDocDir;
462
463    bool Init()
464    {
465       SetGlobalContext(globalContext);
466       SetExcludedSymbols(&excludedSymbols);
467       SetDefines(&::defines);
468       SetImports(&imports);
469       SetInDocumentor(true);
470
471       SetGlobalData(globalData);
472
473       if(argc == 3 || argc == 4)
474       {
475          convertDocDir = CopyString(argv[1]);
476          outputDocDir = CopyString(argv[2]);
477          if(argc == 4)
478             moduleName = CopyString(argv[3]);
479          else
480             moduleName = CopyString("ecere");
481          return true;
482       }
483       return false;
484    }
485
486    void Main()
487    {
488       if(Init())
489       {
490          OpenModule(moduleName);
491       }
492       else
493       {
494          PrintLn($"Syntax: exename <old doc dir> <new doc dir> [<module name>]");
495       }
496       system("pause");
497    }
498
499    void Terminate()
500    {
501       delete moduleName;
502       delete convertDocDir;
503       delete outputDocDir;
504
505       FreeContext(globalContext);
506       FreeExcludedSymbols(excludedSymbols);
507       ::defines.Free(FreeModuleDefine);
508       imports.Free(FreeModuleImport);
509
510       FreeGlobalData(globalData);
511       FreeTypeData(componentsApp);
512       FreeIncludeFiles();
513       delete componentsApp;
514    }
515
516    void OpenModule(const char * filePath)
517    {
518       char moduleName[MAX_LOCATION];
519       char extension[MAX_EXTENSION];
520       Module module = null;
521       static char symbolsDir[MAX_LOCATION];
522
523       FreeContext(globalContext);
524       FreeExcludedSymbols(excludedSymbols);
525       ::defines.Free(FreeModuleDefine);
526       imports.Free(FreeModuleImport);
527
528       FreeGlobalData(globalData);
529       FreeIncludeFiles();
530       if(componentsApp)
531       {
532          FreeTypeData(componentsApp);
533          delete componentsApp;
534       }
535
536       componentsApp = __ecere_COM_Initialize(false, 1, null);
537       SetPrivateModule(componentsApp);
538
539       StripLastDirectory(filePath, symbolsDir);
540       SetSymbolsDir(symbolsDir);
541
542       GetExtension(filePath, extension);
543
544       ImportModule(filePath, normalImport, publicAccess, false);
545
546       if(extension[0] && strcmpi(extension, "so") && strcmpi(extension, "dll") && strcmpi(extension, "dylib"))
547          componentsApp.name = CopyString(filePath);
548
549       for(module = componentsApp.allModules.first; module; module = module.next)
550       {
551          if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
552             break;
553       }
554       if(!module)
555          eModule_LoadStrict(componentsApp, "ecereCOM", publicAccess /*privateAccess*/);
556
557       GetLastDirectory(filePath, moduleName);
558       // Extension, path and lib prefix get removed in Module::name
559       if(extension[0])
560       {
561          StripExtension(moduleName);
562          if((!strcmpi(extension, "so") || !strcmpi(extension, "dylib")) && strstr(moduleName, "lib") == moduleName)
563          {
564             int len = strlen(moduleName) - 3;
565             memmove(moduleName, moduleName + 3, len);
566             moduleName[len] = 0;
567          }
568       }
569
570       ConvertModuleDoc(componentsApp, false);
571    }
572 }
573
574 class DocConvertNamespaceStackFrame
575 {
576    NamespaceDoc namespaceDoc { };
577    Array<ClassDoc> classes { };
578    char * nsPath;
579 }
580
581 enum DocConvertLocType { root, namespaces, functions, parameters, classes, values, fields, properties, conversions, methods };
582
583 class DocConvertIterator : NormalFileSystemIterator
584 {
585    char * nsDir;
586
587    bool getNext;
588    Array<String> nameStack { };
589    Array<DocConvertLocType> typeStack { };
590    Array<DocConvertNamespaceStackFrame> nsStack { };
591    Array<DocConvertNamespaceStackFrame> namespaces { };
592    DocConvertNamespaceStackFrame ns;
593    DefineDoc defineDoc;
594    FunctionDoc functionDoc;
595    ClassDoc classDoc;
596    MethodDoc methodDoc;
597
598    void addType(DocConvertLocType type)
599    {
600       typeStack.Add(type);
601       getNext = true;
602    }
603    void exitType()
604    {
605       typeStack.Remove(typeStack.lastIterator.pointer);
606       getNext = false;
607    }
608
609    DocConvertIterator()
610    {
611       nsDir = new char[MAX_LOCATION];
612    }
613    ~DocConvertIterator()
614    {
615       delete nsDir;
616    }
617 }
618
619 public class NormalFileSystemIterator : FileSystemIterator
620 {
621 public:
622    Array<StackFrame> stack { };
623
624    char * extensions;
625    property char * extensions { set { delete extensions; if(value) extensions = CopyString(value); } }
626
627    ~NormalFileSystemIterator()
628    {
629       delete extensions;
630    }
631
632    void iterate(const char * startPath)
633    {
634       StackFrame frame;
635       char startName[MAX_FILENAME];
636       startName[0] = '\0';
637       GetLastDirectory(startPath, startName);
638
639       onInit(startPath, startName);
640       {
641          frame = StackFrame { };
642          stack.Add(frame);
643          frame.path = CopyString(startPath);
644          frame.listing = FileListing { startPath, extensions = extensions };
645       }
646
647       if(iterateStartPath)
648       {
649          FileAttribs attribs = FileExists(startPath);
650          if(attribs.isDrive)
651             onVolume(startPath);
652          else
653          {
654             if(attribs.isDirectory)
655                onFolder(startPath, startName);
656             else if(attribs.isFile)
657                onFile(startPath, startName);
658          }
659       }
660
661       while(stack.count)
662       {
663          if(frame.listing.Find())
664          {
665             bool peek = frame.listing.stats.attribs.isDirectory && onFolder(frame.listing.path, frame.listing.name);
666             if(!frame.listing.stats.attribs.isDirectory)
667             {
668                onFile(frame.listing.path, frame.listing.name);
669             }
670             else if(peek)
671             {
672                StackFrame newFrame { };
673                stack.Add(newFrame);
674                newFrame.path = CopyString(frame.listing.path);
675                newFrame.listing = FileListing { newFrame.path, extensions = frame.listing.extensions };
676                frame = newFrame;
677             }
678          }
679          else
680          {
681             StackFrame parentFrame = stack.count > 1 ? stack[stack.count - 2] : null;
682             outFolder(parentFrame ? parentFrame.listing.path : startPath, parentFrame ? parentFrame.listing.name : startName, !parentFrame);
683             stack.lastIterator.Remove();
684             if(stack.count)
685                frame = stack.lastIterator.data;
686             else
687                frame = null;
688          }
689       }
690    }
691 }
692
693 public class FileSystemIterator
694 {
695 public:
696    bool iterateStartPath;
697
698    virtual bool onInit(const char * startPath, const char * startName)
699    {
700       return false;
701    }
702
703    virtual bool onFile(const char * filePath, const char * fileName)
704    {
705       return true;
706    }
707
708    virtual bool onFolder(const char * folderPath, const char * folderName)
709    {
710       return true;
711    }
712
713    virtual bool onVolume(const char * volumePath)
714    {
715       return true;
716    }
717
718    virtual void outFolder(const char * folderPath, const char * folderName, bool isRoot)
719    {
720    }
721 }
722
723 public class StackFrame
724 {
725    int tag;
726    char * path;
727    FileListing listing;
728
729    ~StackFrame()
730    {
731       delete path;
732    }
733 };