HtmlPrinter.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. package tools.haxedoc;
  2. import haxe.rtti.CType;
  3. class HtmlPrinter {
  4. static function loadTemplate() {
  5. var hdata = try
  6. // load in current local/web directory
  7. neko.io.File.getContent(neko.Web.getCwd()+"template.xml")
  8. catch( e : Dynamic ) try {
  9. // load in haxe subdirectory (TODO : make it work on linux/osx)
  10. var p = ~/[\/\\]/g.split(neko.Sys.executablePath());
  11. p.pop();
  12. neko.io.File.getContent(p.join("/")+"/std/tools/template.xml");
  13. } catch( e : Dynamic )
  14. default_template;
  15. return Xml.parse(hdata);
  16. }
  17. static var default_template = "<html><body><data/></body></html>";
  18. static var template = loadTemplate();
  19. public var baseUrl : String;
  20. var indexUrl : String;
  21. var fileExtension : String;
  22. var curpackage : String;
  23. var filters : List<String>;
  24. var typeParams : TypeParams;
  25. public function new( baseUrl, fileExtension, indexUrl ) {
  26. this.baseUrl = baseUrl;
  27. this.fileExtension = fileExtension;
  28. this.indexUrl = indexUrl;
  29. filters = new List();
  30. typeParams = new Array();
  31. }
  32. public dynamic function output(str) {
  33. neko.Lib.print(str);
  34. }
  35. public function addFilter(f) {
  36. filters.add(f);
  37. }
  38. public function print(str, ?params : Dynamic ) {
  39. if( params != null )
  40. for( f in Reflect.fields(params) )
  41. str = StringTools.replace(str, "$"+f, Std.string(Reflect.field(params, f)));
  42. output(str);
  43. }
  44. public function process(t) {
  45. processHtml(t,template);
  46. }
  47. public function filtered( path : Path, isPackage : Bool ) {
  48. if( isPackage && path == "Remoting" )
  49. return true;
  50. if( StringTools.endsWith(path,"__") )
  51. return true;
  52. if( filters.isEmpty() )
  53. return false;
  54. for( x in filters )
  55. if( StringTools.startsWith(path,x) )
  56. return false;
  57. return true;
  58. }
  59. function makeUrl( url, text, css ) {
  60. return "<a href=\"" + baseUrl + url + fileExtension + "\" class=\""+css+"\">"+text+"</a>";
  61. }
  62. function prefix( arr : Array<String>, path : String ) {
  63. var arr = arr.copy();
  64. for( i in 0...arr.length )
  65. arr[i] = path + "." + arr[i];
  66. return arr;
  67. }
  68. function makePathUrl( path : Path, css ) {
  69. var p = path.split(".");
  70. var name = p.pop();
  71. var local = (p.join(".") == curpackage);
  72. for( x in typeParams )
  73. if( x == path )
  74. return name;
  75. p.push(name);
  76. if( local )
  77. return makeUrl(p.join("/"),name,css);
  78. return makeUrl(p.join("/"),fmtpath(path),css);
  79. }
  80. function fmtpath(path : String) {
  81. if( path.substr(0,7) == "flash9." )
  82. return "flash."+path.substr(7);
  83. var pack = path.split(".");
  84. if( pack.length > 1 && pack[pack.length-2].charAt(0) == "_" ) {
  85. pack.splice(-2,1);
  86. path = pack.join(".");
  87. }
  88. return path;
  89. }
  90. public function processHtml(t,html : Xml) {
  91. var ht = html.nodeType;
  92. if( ht == Xml.Element ) {
  93. if( html.nodeName == "data" ) {
  94. processPage(t);
  95. return;
  96. }
  97. if( !html.iterator().hasNext() ) {
  98. print(html.toString());
  99. return;
  100. }
  101. print("<");
  102. print(html.nodeName);
  103. for( k in html.attributes() )
  104. print(" "+k+"=\""+html.get(k)+"\"");
  105. print(">");
  106. for( x in html )
  107. processHtml(t,x);
  108. print("</"+html.nodeName+">");
  109. } else if( ht == Xml.Document )
  110. for( x in html )
  111. processHtml(t,x);
  112. else
  113. print(html.toString());
  114. }
  115. public function processPage(t) {
  116. switch(t) {
  117. case TPackage(p,full,list):
  118. processPackage(p,list);
  119. default:
  120. var head = '<a href="#" onclick="javascript:history.back(-1); return false" class="index">Back</a> | '+makeUrl(indexUrl,"Index","index");
  121. print(head);
  122. var inf = TypeApi.typeInfos(t);
  123. typeParams = prefix(inf.params,inf.path);
  124. var p = inf.path.split(".");
  125. p.pop();
  126. curpackage = p.join(".");
  127. switch(t) {
  128. case TClassdecl(c): processClass(c);
  129. case TEnumdecl(e): processEnum(e);
  130. case TTypedecl(t): processTypedef(t);
  131. case TPackage(_,_,_): throw "ASSERT";
  132. }
  133. print(head);
  134. }
  135. }
  136. function processPackage(name,list : Array<TypeTree> ) {
  137. print('<ul class="entry">');
  138. for( e in list ) {
  139. switch e {
  140. case TPackage(name,full,list):
  141. if( filtered(full,true) )
  142. continue;
  143. var isPrivate = name.charAt(0) == "_";
  144. if( !isPrivate )
  145. print('<li><a href="#" class="package" onclick="return toggle(\'$id\')">$name</a><div id="$id" class="package_content">', { id : full.split(".").join("_"), name : name });
  146. var old = curpackage;
  147. curpackage = full;
  148. processPackage(name,list);
  149. curpackage = old;
  150. if( !isPrivate )
  151. print("</div></li>");
  152. default:
  153. var i = TypeApi.typeInfos(e);
  154. if( i.isPrivate || i.path == "@Main" || filtered(i.path,false) )
  155. continue;
  156. print("<li>"+makePathUrl(i.path,"entry")+"</li>");
  157. }
  158. }
  159. print("</ul>");
  160. }
  161. function processInfos(t : TypeInfos) {
  162. if( t.module != null )
  163. print('<div class="importmod">import $module</div>',{ module : t.module });
  164. if( !t.platforms.isEmpty() ) {
  165. print('<div class="platforms">Available in ');
  166. display(t.platforms,output,", ");
  167. print('</div>');
  168. }
  169. if( t.doc != null ) {
  170. print('<div class="classdoc">');
  171. processDoc(t.doc);
  172. print('</div>');
  173. }
  174. }
  175. function processClass(c : Classdef) {
  176. print('<div class="classname">');
  177. if( c.isExtern )
  178. keyword("extern");
  179. if( c.isPrivate )
  180. keyword("private");
  181. if( c.isInterface )
  182. keyword("interface");
  183. else
  184. keyword("class");
  185. print(fmtpath(c.path));
  186. if( c.params.length != 0 ) {
  187. print("&lt;");
  188. print(c.params.join(", "));
  189. print("&gt;");
  190. }
  191. print('</div>');
  192. if( c.superClass != null ) {
  193. print('<div class="extends">extends ');
  194. processPath(c.superClass.path,c.superClass.params);
  195. print('</div>');
  196. }
  197. for( i in c.interfaces ) {
  198. print('<div class="implements">implements ');
  199. processPath(i.path,i.params);
  200. print('</div>');
  201. }
  202. if( c.tdynamic != null ) {
  203. var d = new List();
  204. d.add(c.tdynamic);
  205. print('<div class="implements">implements ');
  206. processPath("Dynamic",d);
  207. print('</div>');
  208. }
  209. processInfos(c);
  210. print('<dl>');
  211. for( f in c.fields )
  212. processClassField(c.platforms,f,false);
  213. for( f in c.statics )
  214. processClassField(c.platforms,f,true);
  215. print('</dl>');
  216. }
  217. function processClassField(platforms : Platforms,f : ClassField,stat) {
  218. if( !f.isPublic )
  219. return;
  220. var oldParams = typeParams;
  221. if( f.params != null )
  222. typeParams = typeParams.concat(prefix(f.params,f.name));
  223. print('<dt>');
  224. if( stat ) keyword("static");
  225. var isMethod = false;
  226. switch( f.type ) {
  227. case CFunction(args,ret):
  228. if( f.get == RNormal && (f.set == RNormal || f.set == RF9Dynamic) ) {
  229. isMethod = true;
  230. if( f.set == RF9Dynamic )
  231. keyword("f9dynamic");
  232. keyword("function");
  233. print(f.name);
  234. if( f.params != null )
  235. print("&lt;"+f.params.join(", ")+"&gt;");
  236. print("(");
  237. var me = this;
  238. display(args,function(a) {
  239. if( a.opt )
  240. me.print("?");
  241. if( a.name != null && a.name != "" ) {
  242. me.print(a.name);
  243. me.print(" : ");
  244. }
  245. me.processType(a.t);
  246. },", ");
  247. print(") : ");
  248. processType(ret);
  249. }
  250. default:
  251. }
  252. if( !isMethod ) {
  253. keyword("var");
  254. print(f.name);
  255. if( f.get != RNormal || f.set != RNormal )
  256. print("("+rightsStr(f.get)+","+rightsStr(f.set)+")");
  257. print(" : ");
  258. processType(f.type);
  259. }
  260. if( f.platforms.length != platforms.length ) {
  261. print('<div class="platforms">Available in ');
  262. display(f.platforms,output,", ");
  263. print('</div>');
  264. }
  265. print('</dt>');
  266. print('<dd>');
  267. processDoc(f.doc);
  268. print('</dd>');
  269. if( f.params != null )
  270. typeParams = oldParams;
  271. }
  272. function processEnum(e : Enumdef) {
  273. print('<div class="classname">');
  274. if( e.isExtern )
  275. keyword("extern");
  276. if( e.isPrivate )
  277. keyword("private");
  278. keyword("enum");
  279. print(fmtpath(e.path));
  280. if( e.params.length != 0 ) {
  281. print("&lt;");
  282. print(e.params.join(", "));
  283. print("&gt;");
  284. }
  285. print('</div>');
  286. processInfos(e);
  287. print('<dl>');
  288. for( c in e.constructors ) {
  289. print('<dt>');
  290. print(c.name);
  291. if( c.args != null ) {
  292. print("(");
  293. var me = this;
  294. display(c.args,function(a) {
  295. if( a.opt )
  296. me.print("?");
  297. me.print(a.name);
  298. me.print(" : ");
  299. me.processType(a.t);
  300. },",");
  301. print(")");
  302. }
  303. print("</dt>");
  304. print("<dd>");
  305. processDoc(c.doc);
  306. print("</dd>");
  307. }
  308. print('</dl>');
  309. }
  310. function processTypedef(t : Typedef) {
  311. print('<div class="classname">');
  312. if( t.isPrivate )
  313. keyword("private");
  314. keyword("typedef");
  315. print(fmtpath(t.path));
  316. if( t.params.length != 0 ) {
  317. print("&lt;");
  318. print(t.params.join(", "));
  319. print("&gt;");
  320. }
  321. print('</div>');
  322. processInfos(t);
  323. if( t.platforms.length == 0 ) {
  324. processTypedefType(t.type,t.platforms,t.platforms);
  325. return;
  326. }
  327. var platforms = new List();
  328. for( p in t.platforms )
  329. platforms.add(p);
  330. for( p in t.types.keys() ) {
  331. var td = t.types.get(p);
  332. var support = new List();
  333. for( p2 in platforms )
  334. if( TypeApi.typeEq(td,t.types.get(p2)) ) {
  335. platforms.remove(p2);
  336. support.add(p2);
  337. }
  338. if( support.length == 0 )
  339. continue;
  340. processTypedefType(td,t.platforms,support);
  341. }
  342. }
  343. function processTypedefType(t,all,platforms) {
  344. switch( t ) {
  345. case CAnonymous(fields):
  346. print('<dl>');
  347. for( f in fields ) {
  348. processClassField(all,{
  349. name : f.name,
  350. type : f.t,
  351. isPublic : true,
  352. doc : null,
  353. get : RNormal,
  354. set : RNormal,
  355. params : null,
  356. platforms : platforms,
  357. },false);
  358. }
  359. print('</dl>');
  360. default:
  361. if( all.length != platforms.length ) {
  362. print('<div class="platforms">Defined in ');
  363. display(platforms,output,", ");
  364. print('</div>');
  365. }
  366. print('<div class="typedef">= ');
  367. processType(t);
  368. print('</div>');
  369. }
  370. }
  371. function processPath( path : Path, ?params : List<CType> ) {
  372. print(makePathUrl(path,"type"));
  373. if( params != null && !params.isEmpty() ) {
  374. print("&lt;");
  375. for( t in params )
  376. processType(t);
  377. print("&gt;");
  378. }
  379. }
  380. function processType( t : CType ) {
  381. switch( t ) {
  382. case CUnknown:
  383. print("Unknown");
  384. case CEnum(path,params):
  385. processPath(path,params);
  386. case CClass(path,params):
  387. processPath(path,params);
  388. case CTypedef(path,params):
  389. processPath(path,params);
  390. case CFunction(args,ret):
  391. if( args.isEmpty() ) {
  392. processPath("Void");
  393. print(" -> ");
  394. }
  395. for( a in args ) {
  396. if( a.opt )
  397. print("?");
  398. if( a.name != null && a.name != "" )
  399. print(a.name+" : ");
  400. processTypeFun(a.t,true);
  401. print(" -> ");
  402. }
  403. processTypeFun(ret,false);
  404. case CAnonymous(fields):
  405. print("{ ");
  406. var me = this;
  407. display(fields,function(f) {
  408. me.print(f.name+" : ");
  409. me.processType(f.t);
  410. },", ");
  411. print("}");
  412. case CDynamic(t):
  413. if( t == null )
  414. processPath("Dynamic");
  415. else {
  416. var l = new List();
  417. l.add(t);
  418. processPath("Dynamic",l);
  419. }
  420. }
  421. }
  422. function processTypeFun( t : CType, isArg ) {
  423. var parent = switch( t ) { case CFunction(_,_): true; case CEnum(n,_): isArg && n == "Void"; default : false; };
  424. if( parent )
  425. print("(");
  426. processType(t);
  427. if( parent )
  428. print(")");
  429. }
  430. function rightsStr(r) {
  431. return switch(r) {
  432. case RNormal: "default";
  433. case RNo: "null";
  434. case RMethod(m): m;
  435. case RDynamic: "dynamic";
  436. case RF9Dynamic: "f9dynamic";
  437. }
  438. }
  439. function keyword(w) {
  440. print('<span class="kwd">'+w+' </span>');
  441. }
  442. function processDoc(doc : String) {
  443. if( doc == null )
  444. return;
  445. // unixify line endings
  446. doc = doc.split("\r\n").join("\n").split("\r").join("\n");
  447. // trim stars
  448. doc = ~/^([ \t]*)\*+/gm.replace(doc, "$1");
  449. doc = ~/\**[ \t]*$/gm.replace(doc, "");
  450. // process [] blocks
  451. var rx = ~/\[/;
  452. var tmp = new StringBuf();
  453. var codes = new List();
  454. while (rx.match(doc)) {
  455. tmp.add( rx.matchedLeft() );
  456. var code = rx.matchedRight();
  457. var brackets = 1;
  458. var i = 0;
  459. while( i < code.length && brackets > 0 ) {
  460. switch( code.charCodeAt(i++) ) {
  461. case 91: brackets++;
  462. case 93: brackets--;
  463. }
  464. }
  465. doc = code.substr(i);
  466. code = code.substr(0, i-1);
  467. code = ~/&/g.replace(code, "&amp;");
  468. code = ~/</g.replace(code, "&lt;");
  469. code = ~/>/g.replace(code, "&gt;");
  470. var tag = "##__code__"+codes.length+"##";
  471. if( code.indexOf('\n') != -1 ) {
  472. tmp.add("<pre>");
  473. tmp.add(tag);
  474. tmp.add("</pre>");
  475. codes.add(code.split("\t").join(" "));
  476. } else {
  477. tmp.add("<code>");
  478. tmp.add(tag);
  479. tmp.add("</code>");
  480. codes.add(code);
  481. }
  482. }
  483. tmp.add(doc);
  484. // separate into paragraphs
  485. var parts = ~/\n[ \t]*\n/g.split(tmp.toString());
  486. if( parts.length == 1 )
  487. doc = parts[0];
  488. else
  489. doc = Lambda.map(parts,function(x) { return "<p>"+StringTools.trim(x)+"</p>"; }).join("\n");
  490. // put back code parts
  491. var i = 0;
  492. for( c in codes )
  493. doc = doc.split("##__code__"+(i++)+"##").join(c);
  494. print(doc);
  495. }
  496. function display<T>( l : List<T>, f : T -> Void, sep : String ) {
  497. var first = true;
  498. for( x in l ) {
  499. if( first )
  500. first = false;
  501. else
  502. print(sep);
  503. f(x);
  504. }
  505. }
  506. }