Nicolas Cannasse 19 年之前
父节点
当前提交
de5e970eb0

+ 432 - 0
std/tools/haxedoc/HtmlPrinter.hx

@@ -0,0 +1,432 @@
+package tools.haxedoc;
+import tools.haxedoc.Type;
+
+class HtmlPrinter {
+
+	static function loadTemplate() {
+		var hdata = try
+			// load in current local/web directory
+			neko.io.File.getContent(neko.Web.getCwd()+"template.xml")
+		catch( e : Dynamic ) try {
+			// load in haxe subdirectory (TODO : make it work on linux/osx)
+			var p = ~/[\/\\]/g.split(neko.Sys.executablePath());
+			p.pop();
+			neko.io.File.getContent(p.join("/")+"/std/tools/template.xml");
+		} catch( e : Dynamic )
+			default_template;
+		return Xml.parse(hdata);
+	}
+
+	static var default_template = "<html><body><data/></body></html>";
+	static var template = loadTemplate();
+	
+	public var baseUrl : String;
+	var indexUrl : String;
+	var fileExtension : String;
+	var curpackage : String;
+	var filters : List<String>;
+	var typeParams : TypeParams;
+		
+	public function new( baseUrl, fileExtension, indexUrl ) {
+		this.baseUrl = baseUrl;
+		this.fileExtension = fileExtension;
+		this.indexUrl = indexUrl;
+		filters = new List();
+		typeParams = new Array();
+	}
+	
+	public function output(str) {
+		neko.Lib.print(str);
+	}
+	
+	public function addFilter(f) {
+		filters.add(f);
+	}
+
+	public function print(str, ?params : Dynamic ) {
+		if( params != null )
+			for( f in Reflect.fields(params) )
+				str = StringTools.replace(str, "$"+f, Std.string(Reflect.field(params, f)));
+		output(str);
+	}
+	
+	public function process(t) {
+		processHtml(t,template);
+	}
+	
+	public function filtered( path : Path, isPackage : Bool ) {
+		if( isPackage && path == "Remoting" )
+			return true;
+		if( StringTools.endsWith(path,"__") )
+			return true;
+		if( filters.isEmpty() )
+			return false;
+		for( x in filters )
+			if( StringTools.startsWith(path,x) )
+				return false;
+		return true;
+	}
+	
+	function makeUrl( url, text, css ) {
+		return "<a href=\"" + baseUrl + url + fileExtension + "\" class=\""+css+"\">"+text+"</a>";
+	}
+	
+	function makePathUrl( path : Path, css ) {
+		var p = path.split(".");
+		var name = p.pop();
+		var local = (p.join(".") == curpackage);
+		if( local )
+			for( x in typeParams )
+				if( x == name )
+					return name;
+		p.push(name);
+		if( local )
+			return makeUrl(p.join("/"),name,css);
+		return makeUrl(p.join("/"),f9path(path),css);
+	}
+	
+	function f9path(path : String) {
+		if( path.substr(0,7) == "flash9." )
+			return "flash."+path.substr(7);
+		return path;
+	}
+	
+	public function processHtml(t,html : Xml) {
+		switch( html.nodeType ) {
+		case Xml.Element:
+			if( html.nodeName == "data" ) {
+				processPage(t);
+				return;
+			}
+			if( !html.iterator().hasNext() ) {
+				print(html.toString());
+				return;
+			}
+			print("<");
+			print(html.nodeName);
+			for( k in html.attributes() )
+				print(" "+k+"=\""+html.get(k)+"\"");
+			print(">");
+			for( x in html )
+				processHtml(t,x);
+			print("</"+html.nodeName+">");
+		case Xml.Document:
+			for( x in html )
+				processHtml(t,x);
+		default:
+			print(html.toString());
+		}
+	}
+	
+	public function processPage(t) {
+		switch(t) {
+		case TPackage(p,full,list):
+			processPackage(p,list);
+		default:
+			var head = '<a href="#" onclick="javascript:history.back(-1); return false" class="index">Back</a> | '+makeUrl(indexUrl,"Index","index");
+			print(head);
+			var inf = TypeApi.typeInfos(t);
+			typeParams = inf.params;
+			var p = inf.path.split(".");
+			p.pop();
+			curpackage = p.join(".");
+			switch(t) {
+			case TClassdecl(c): processClass(c);
+			case TEnumdecl(e): processEnum(e);
+			case TTypedecl(t): processTypedef(t);
+			case TPackage(_,_,_): throw "ASSERT";
+			}			
+			print(head);
+		}
+	}
+	
+	function processPackage(name,list : Array<TypeTree> ) {
+		print('<ul class="entry">');
+		for( e in list ) {
+			switch e {
+			case TPackage(name,full,list):
+				if( filtered(full,true) )					
+					continue;
+				print('<li><a href="#" class="package" onclick="toggle(\'$id\')">$name</a><div id="$id" class="package_content">', { id : full.split(".").join("_"), name : name });
+				var old = curpackage;
+				curpackage = full;
+				processPackage(name,list);
+				curpackage = old;
+				print("</div></li>");
+			default:
+				var i = TypeApi.typeInfos(e);
+				if( i.isPrivate || i.path == "@Main" || filtered(i.path,false) )
+					continue;
+				print("<li>"+makePathUrl(i.path,"entry")+"</li>");
+			}
+		}
+		print("</ul>");
+	}
+	
+	function processInfos(t : TypeInfos) {
+		if( t.module != null )
+			print('<div class="importmod">import $module</div>',{ module : t.module });
+		if( !t.platforms.isEmpty() ) {
+			print('<div class="platforms">Available in ');
+			display(t.platforms,output,", ");
+			print('</div>');
+		}		
+		if( t.doc != null ) {
+			print('<div class="classdoc">');
+			processDoc(t.doc);
+			print('</div>');
+		}
+	}
+
+	function processClass(c : Class) {		
+		print('<div class="classname">');
+		if( c.isExtern )
+			keyword("extern");
+		if( c.isPrivate )
+			keyword("private");
+		if( c.isInterface )
+			keyword("interface");
+		else
+			keyword("class");
+		print(f9path(c.path));
+		if( c.params.length != 0 ) {
+			print("&lt;");
+			print(c.params.join(", "));
+			print("&gt;");
+		}
+		print('</div>');
+		if( c.superClass != null ) {
+			print('<div class="extends">extends ');
+			processPath(c.superClass.path,c.superClass.params);
+			print('</div>');
+		}
+		for( i in c.interfaces ) {
+			print('<div class="implements">implements ');
+			processPath(i.path,i.params);
+			print('</div>');
+		}
+		if( c.dynamic != null ) {
+			var d = new List();
+			d.add(c.dynamic);
+			print('<div class="implements">implements ');
+			processPath("Dynamic",d);
+			print('</div>');
+		}
+		processInfos(c);
+		print('<dl>');
+		for( f in c.fields )
+			processClassField(c.platforms,f,false);
+		for( f in c.statics )
+			processClassField(c.platforms,f,true);
+		print('</dl>');
+	}
+	
+	function processClassField(platforms : Platforms,f : ClassField,stat) {
+		if( !f.isPublic )
+			return;
+		print('<dt>');
+		if( stat ) keyword("static");
+		var isMethod = false;
+		switch( f.type ) {
+		case TFunction(args,ret):
+			if( f.get == RNormal && (f.set == RNormal || f.set == RF9Dynamic) ) {
+				isMethod = true;
+				if( f.set == RF9Dynamic )
+					keyword("f9dynamic");
+				keyword("function");
+				print(f.name);				
+				if( f.params != null )
+					print("&lt;"+f.params.join(", ")+"&gt;");				
+				print("(");
+				var me = this;
+				display(args,function(a) {
+					if( a.opt )
+						me.print("?");
+					me.print(a.name);
+					me.print(" : ");
+					me.processType(a.t);					
+				},", ");
+				print(") : ");
+				processType(ret);
+			}
+		default:
+		}
+		if( !isMethod ) {
+			keyword("var");
+			print(f.name);
+			if( f.get != RNormal || f.set != RNormal )
+				print("("+rightsStr(f.get)+","+rightsStr(f.set)+")");
+			print(" : ");
+			processType(f.type);			
+		}
+		if( f.platforms.length != platforms.length ) {
+			print('<div class="platforms">Available in ');
+			display(f.platforms,output,", ");
+			print('</div>');
+		}
+		print('</dt>');
+		print('<dd>');
+		processDoc(f.doc);
+		print('</dd>');
+	}
+	
+	function processEnum(e : Enum) {
+		print('<div class="classname">');
+		if( e.isExtern )
+			keyword("extern");
+		if( e.isPrivate )
+			keyword("private");
+		keyword("enum");
+		print(f9path(e.path));		
+		if( e.params.length != 0 ) {
+			print("&lt;");
+			print(e.params.join(", "));
+			print("&gt;");
+		}
+		print('</div>');
+		processInfos(e);
+		print('<dl>');
+		for( c in e.constructors ) {
+			print('<dt>');
+			print(c.name);
+			if( c.args != null ) {
+				print("(");
+				var me = this;
+				display(c.args,function(a) {
+					if( a.opt )
+						me.print("?");
+					me.print(a.name);
+					me.print(" : ");
+					me.processType(a.t);					
+				},",");
+				print(")");
+			}
+			print("</dt>");
+			print("<dd>");
+			processDoc(c.doc);			
+			print("</dd>");
+		}
+		print('</dl>');
+	}
+	
+	function processTypedef(t : Typedef) {
+		print('<div class="classname">');
+		if( t.isPrivate )
+			keyword("private");
+		keyword("typedef");
+		print(f9path(t.path));
+		if( t.params.length != 0 ) {
+			print("&lt;");
+			print(t.params.join(", "));
+			print("&gt;");
+		}
+		print('</div>');
+		processInfos(t);
+		switch( t.type ) {
+		case TAnonymous(fields):
+			print('<dl>');
+			for( f in fields ) {
+				processClassField(t.platforms,{
+					name : f.name,
+					type : f.t,
+					isPublic : true,
+					doc : null,
+					get : RNormal,
+					set : RNormal,
+					params : null,
+					platforms : t.platforms,
+				},false);
+			}
+			print('</dl>');
+		default:
+			print('<div class="typedef">= ');
+			processType(t.type);
+			print('</div>');
+		}
+	}
+	
+	function processPath( path : Path, ?params : List<Type> ) {
+		print(makePathUrl(path,"type"));
+		if( params != null && !params.isEmpty() ) {
+			print("&lt;");
+			for( t in params )
+				processType(t);
+			print("&gt;");
+		}
+	}
+	
+	function processType( t : Type ) {
+		switch( t ) {
+		case TUnknown:
+			print("Unknown");
+		case TEnum(path,params):
+			processPath(path,params);
+		case TClass(path,params):
+			processPath(path,params);
+		case TTypedef(path,params):
+			processPath(path,params);
+		case TFunction(args,ret):
+			if( args.isEmpty() ) {
+				processPath("Void");
+				print(" -> ");
+			}
+			for( a in args ) {
+				if( a.opt )
+					print("?");
+				print(a.name+" : ");
+				processType(a.t);
+				print(" -> ");
+			}
+			processType(ret);
+		case TAnonymous(fields):
+			print("{ ");
+			var me = this;
+			display(fields,function(f) {
+				me.print(f.name+" : ");
+				me.processType(f.t);				
+			},", ");
+			print("}");
+		case TDynamic(t):
+			if( t == null )
+				processPath("Dynamic");
+			else {
+				var l = new List();
+				l.add(t);
+				processPath("Dynamic",l);
+			}
+		}
+	}
+	
+	function rightsStr(r) {
+		return switch(r) {
+		case RNormal: "default";
+		case RNo: "null";
+		case RMethod(m): m;
+		case RDynamic: "dynamic";
+		case RF9Dynamic: "f9dynamic";
+		}
+	}
+	
+	function keyword(w) {
+		print('<span class="kwd">'+w+' </span>');
+	}
+	
+	function processDoc(doc) {
+		if( doc == null )
+			return;
+		doc = ~/\[([^\]]+)\]/g.replace(doc,"<code>$1</code>");
+		print(doc);
+	}
+	
+	function display<T>( l : List<T>, f : T -> Void, sep : String ) {
+		var first = true;
+		for( x in l ) {
+			if( first )
+				first = false;
+			else
+				print(sep);
+			f(x);
+		}
+	}
+
+}

+ 86 - 665
std/tools/haxedoc/Main.hx

@@ -23,706 +23,127 @@
  * DAMAGE.
  */
 package tools.haxedoc;
-
-import neko.Lib;
-import neko.Web;
-
-private class Url {
-	public static var base : String;
-	public static var extension : String = "";
-	public static var index : String;
-	public static var buffer : StringBuf;
-	public static function make( params, css, text ) {
-		return "<a href=\""+base + params + extension+"\" class=\""+css+"\">"+text+"</a>";
-	}
-}
-
-private enum DocType {
-	tunknown;
-	tclass( name : String, params : Array<DocType> );
-	tenum( name : String, params : Array<DocType> );
-	tanon( fields : Array<{ name : String, t : DocType  }> );
-	tdynamic( t : DocType );
-	tfunction( params : Array<{ name : String, t : DocType }>, ret : DocType );
-	tparam( classpath : String, name : String );
-	tconstr( fields : Array<{ name : String, t : DocType }> );
-	ttype( name : String, params : Array<DocType> );
-}
-
-private class DocField {
-
-	public var name : String;
-	public var isStatic : Bool;
-	public var type : DocType;
-	public var doc : String;
-	var parent : DocClass;
-
-	public function new( name, s, t, p ) {
-		this.name = name;
-		isStatic = s;
-		type = t;
-		parent = p;
-	}
-
-	public function isVar() {
-		if( type == null )
-			return true;
-		switch type {
-		case tfunction(_,_):
-			return false;
-		default:
-			return true;
-		}
-	}
-
-	public function link(name : String ) {
-		var path = name.split(".");
-		var local = true;
-		for( i in 0...path.length-1 ) {
-			if( path[i] != parent.spath[i] ) {
-				local = false;
-				break;
-			}
-		}
-		if( local ) {
-			var url = path.join("/");
-			return Url.make(url,"type",path.pop());
-		}
-		return Url.make(path.join("/"),"type",DocClass.flash9(name));
-	}
-
-	function paramsToString( params : Array<DocType> ) {
-		if( params.length == 0 )
-			return "";
-		var ps = new StringBuf();
-		ps.add("&lt;");
-		var first = true;
-		for( p in params ) {
-			if( first )
-				first = false;
-			else
-				ps.add(",");
-			ps.add(typeToString(p));
-		}
-		ps.add("&gt;");
-		return ps.toString();
-	}
-
-	function typeToString(t) {
-		switch t {
-		case tunknown:
-			return "Unknown";
-		case tclass(name,params):
-			return link(name)+paramsToString(params);
-		case ttype(name,params):
-			return link(name)+paramsToString(params);
-		case tenum(name,params):
-			return link(name)+paramsToString(params);
-		case tanon(fields):
-			var buf = new StringBuf();
-			var first = true;
-			buf.add("{");
-			for( f in fields ) {
-				if( first )
-					first = false;
-				else
-					buf.add(", ");
-				buf.add(f.name);
-				buf.add(" : ");
-				buf.add(typeToString(f.t));
-			}
-			buf.add(" }");
-			return buf.toString();
-		case tdynamic(t):
-			if( t == null )
-				return link("Dynamic");
-			return link("Dynamic") + "&lt;" + typeToString(t) + "&gt;";
-		case tfunction(params,ret):
-			var buf = new StringBuf();
-			if( params.length == 0 )
-				buf.add("Void -> ");
-			else {
-				for( p in params ) {
-					if( p.name != "" ) {
-						buf.add(p.name);
-						buf.add(" : ");
-					}
-					buf.add(funToString(p.t,true));
-					buf.add(" -> ");
-				}
-			}
-			buf.add(funToString(ret,false));
-			return buf.toString();
-		case tparam(cl,name):
-			return if( cl != parent.path ) link(cl) + "." + name else name;
-		case tconstr(params):
-			var s = new StringBuf();
-			s.add("(");
-			var first = true;
-			for( p in params ) {
-				if( first )
-					first = false;
-				else
-					s.add(", ");
-				s.add(p.name);
-				s.add(" : ");
-				s.add(typeToString(p.t));
-			}
-			s.add(")");
-			return s.toString();
-		}
-		return null;
-	}
-
-	function funToString( t, isarg ) {
-		var parent =
-		switch( t ) {
-		case tfunction(_,_): true;
-		case tenum(name,_): isarg && name == "Void";
-		default: false;
-		}
-		if( parent )
-			return "(" + typeToString(t) + ")";
-		else
-			return typeToString(t);
-	}
-
-	public function methToString( t ) {
-		switch( t ) {
-		case tfunction(params,ret):
-			var s = new StringBuf();
-			s.add("(");
-			var first = true;
-			for( p in params ) {
-				if( first )
-					first = false;
-				else
-					s.add(", ");
-				if( p.name == "" )
-					return typeToString(t);
-				s.add(p.name);
-				s.add(" : ");
-				s.add(typeToString(p.t));
-			}
-			s.add(") : ");
-			s.add(typeToString(ret));
-			return s.toString();
-		default:
-			return typeToString(t);
-		}
-	}
-
-}
-
-private class DocClass {
-
-	public var path : String;
-	public var spath : Array<String>;
-	public var module : String;
-	public var name : String;
-	public var doc : String;
-	public var params : Array<String>;
-	public var fields : Array<DocField>;
-	public var isPrivate : Bool;
-
-	public function new( path ) {
-		this.path = path;
-		spath = path.split(".");
-		fields = new Array();
-		params = new Array();
-	}
-
-	public static function flash9( p : String ) : String {
-		if( p.substr(0,7) == "flash9." )
-			return "flash."+p.substr(7,p.length-7);
-		return p;
-	}
-
-	function genName( s : StringBuf ) {
-		s.add("class ");
-		s.add(flash9(path));
-	}
-
-	function genBody( s : StringBuf ) {
-		for( f in fields ) {
-			s.add("<dt>");
-			if( f.isStatic )
-				s.add("static ");
-			if( f.isVar() )
-				s.add("var ");
-			else
-				s.add("function ");
-			s.add(f.name);
-			if( f.isVar() )
-				s.add(" : ");
-			s.add(f.methToString(f.type));
-			s.add("</dt>");
-			s.add("<dd>");
-			if( f.doc != null ) s.add(f.doc);
-			s.add("</dd>");
-		}
-	}
-
-	public function toString() {
-		var s = new StringBuf();
-		s.add("<div class=\"classname\">");
-		if( isPrivate )
-			s.add("private ");
-		genName(s);
-		if( params.length > 0 ) {
-			s.add("&lt;");
-			s.add(params.join(", "));
-			s.add("&gt;");
-		}
-		s.add("</div>");
-		if( module != null ) {
-			s.add("<div class=\"importmod\">");
-			s.add("import "+module);
-			s.add("</div>");
-		}
-		if( doc != null ) {
-			s.add("<div class=\"classdoc\">");
-			s.add(doc);
-			s.add("</div>");
-		}
-		s.add("<dl>");
-		genBody(s);
-		s.add("</dl>");
-		return s.toString();
-	}
-
-}
-
-
-private class DocEnum extends DocClass {
-
-	override function genName( s : StringBuf ) {
-		s.add("enum ");
-		s.add(path);
-	}
-
-	override function genBody( s : StringBuf ) {
-		for( f in fields ) {
-			s.add("<dt>");
-			s.add(f.name);
-			if( f.type != null )
-				s.add(f.methToString(f.type));
-			s.add("</dt>");
-			s.add("<dd>");
-			if( f.doc != null ) s.add(f.doc);
-			s.add("</dd>");
-		}
-	}
-
-}
-
-private class DocTypedef extends DocClass {
-
-	public var t : DocType;
-
-	override function genBody( s : StringBuf ) {
-		if( t == null ) {
-			super.genBody(s);
-			return;
-		}
-		s.add("<dt> = ");
-		s.add(new DocField("",false,t,this).methToString(t));
-		s.add("</dt>");
-	}
-
-	override function genName( s : StringBuf ) {
-		s.add("typedef ");
-		s.add(path);
-	}
-}
-
-private enum DocEntry {
-	eclass( c : DocClass );
-	epackage( name : String, fullname : Array<String>, childs : Array<DocEntry> );
-}
+import tools.haxedoc.Type;
 
 class Main {
 
-	static var entries = new Array();
+	static var parser = new XmlParser();
 
-	static function processType( x : Xml ) {
-		var p = new Array();
-		switch( x.nodeName )  {
-		case "unknown":
-			return tunknown;
-		case "c":
-			return tclass(x.get("path"),Lambda.amap(Lambda.array(x.elements()),processType));
-		case "t":
-			return ttype(x.get("path"),Lambda.amap(Lambda.array(x.elements()),processType));
-		case "e":
-			var path = x.get("path").split(".");
-			if( path.length >= 2 ) {
-				var c = path[path.length-2].charAt(0);
-				if( c >= "A" && c <= "Z" ) {
-					var name = path.pop();
-					return tparam(path.join("."),name);
-				}
-			}
-			return tenum(x.get("path"),Lambda.amap(Lambda.array(x.elements()),processType));
-		case "f":
-			var params = x.get("a").split(":");
-			var it = x.elements();
-			var pl = Lambda.amap(Lambda.array(params.iterator()),function(name) {
-				return {
-					name : name,
-					t : processType(it.next())
-				};
-			});
-			return tfunction(pl,processType(it.next()));
-		case "a":
-			var fields = Lambda.amap(Lambda.array(x.elements()),function(x : Xml) {
-				return { name : x.nodeName, t : processType(x.firstElement()) };
-			});
-			return tanon(fields);
-		case "d":
-			var x = x.firstElement();
-			return tdynamic( if( x == null) null else processType(x) );
-		default:
-			throw ("Unknown type "+x.nodeName);
-		}
-	}
-
-	static function docFormat( doc : String ) : String {
-		doc = ~/\[([^\]]+)\]/g.replace(doc,"<code>$1</code>");
-		return doc;
-	}
-
-	static function processField( c : DocClass, x : Xml ) {
-		var stat = x.get("static") == "1";
-		var nl = x.elements();
-		var t = processType(nl.next());
-		var f = new DocField(x.nodeName,stat,t,c);
-		var doc = nl.next();
-		if( doc != null )
-			f.doc = docFormat(doc.firstChild().nodeValue);
-		return f;
-	}
-
-	static function processClass(x : Xml) {
-		var path = x.get("path");
-		if( StringTools.endsWith(path,"__") )
-			return;
-		if( findEntry(entries,path.split(".")) != null ) {
-			// MERGE ?
-			return;
-		}
-		var c : DocClass;
-		switch( x.nodeName ) {
-		case "class":
-			c = new DocClass(path);
-			for( m in x.elements() ) {
-				if( m.nodeName == "haxe_doc" ) {
-					c.doc = docFormat(m.firstChild().nodeValue);
-					continue;
-				}
-				if( m.nodeName == "extends" ) {
-					// TODO
-					continue;
-				}
-				if( m.nodeName == "implements" ) {
-					// TODO
-					continue;
-				}
-				if( m.get("public") == "1" )
-					c.fields.push(processField(c,m));
-			}
-		case "typedef":
-			var s = new DocTypedef(path);
-			var t = processType(x.firstElement());
-			switch( t ) {
-			case tanon(fields):
-				for( f in fields ) {
-					var f = new DocField(f.name,false,f.t,s);
-					s.fields.push(f);
-				}
-			default:
-				s.t = t;
-			}
-			c = s;
-		case "enum":
-			var e = new DocEnum(path);
-			c = e;
-			for( m in x.elements() ) {
-				if( m.nodeName == "haxe_doc" ) {
-					c.doc = docFormat(m.firstChild().nodeValue);
-					continue;
-				}
-				var l = Lambda.array(m.elements());
-				var last = l[l.length-1];
-				var doc = if( last == null || last.nodeName != "haxe_doc" ) null else docFormat(l.pop().firstChild().nodeValue);
-				var t = if( m.get("a") == null ) null else {
-					var names = m.get("a").split(":");
-					var params = Lambda.amap(names,function(name) {
-						return {
-							name : name,
-							t : processType(l.pop())
-						};
-					});
-					tconstr(params);
-				}
-				var f = new DocField(m.nodeName,false,t,c);
-				f.doc = doc;
-				c.fields.push(f);
-			}
-		default:
-			throw x.nodeName;
-		}
-		c.isPrivate = x.get("private") == "1";
-		c.module = x.get("module");
-		c.params = x.get("params").split(":");
-		c.fields.sort(function(f1 : DocField,f2 : DocField) {
-			if( f1.isStatic && !f2.isStatic )
-				return 1;
-			var v1 = f1.isVar();
-			var v2 = f2.isVar();
-			if( v1 && !v2 )
-				return -1;
-			if( v2 && !v1 )
-				return 1;
-			if( f1.name > f2.name )
-				return 1;
-			return -1;
-		});
-		addEntry(c);
-	}
-
-	static function addEntry( c : DocClass ) {
-		var path = c.path.split(".");
-		var pack = entries;
-		if( path.length > 0 ) {
-			c.name = path.pop();
-			var acc = new Array();
-			for( x in path ) {
-				var found = false;
-				for( p in pack ) {
-					switch p {
-					case epackage(name,_,p):
-						if( name == x ) {
-							pack = p;
-							found = true;
-							break;
-						}
-					default:
-					}
-				}
-				acc.push(x);
-				if( !found ) {
-					var p = new Array();
-					pack.push(epackage(x,acc.copy(),p));
-					pack = p;
-				}
-			}
-		} else
-			c.name = c.path;
-		pack.push(eclass(c));
-	}
-
-	static function findEntry( pack : Array<DocEntry>, path : Array<String>) {
-		for( p in path ) {
-			var found = false;
-			for( e in pack ) {
-				switch e {
-				case eclass(c): if( c.name.toLowerCase() == p.toLowerCase() ) return c;
-				case epackage(name,_,newpack):
-					if( name == p ) {
-						found = true;
-						pack = newpack;
-						break;
-					}
-				}
-			}
-			if( !found )
-				return null;
-		}
-		return null;
-	}
-
-	static function sortEntries( p : Array<DocEntry> ) {
-		p.sort(function(e1 : DocEntry,e2 : DocEntry) {
-			var n1 = switch e1 {
-				case epackage(p,_,_) : " "+p;
-				case eclass(c) : c.name;
-			};
-			var n2 = switch e2 {
-				case epackage(p,_,_) : " "+p;
-				case eclass(c) : c.name;
-			};
-			if( n1 > n2 )
-				return 1;
-			return -1;
-		});
-		for( e in p ) {
-			switch e {
-			case epackage(_,_,p):
-				sortEntries(p);
-			default:
-			}
-		}
+	static function loadFile(file,platform) {
+		var data = neko.io.File.getContent(neko.Web.getCwd()+file);
+		var x = Xml.parse(data).firstElement();
+		parser.process(x,platform);
 	}
 
-	static function display(p : Array<DocEntry> ) {
-		print("<ul class=\"entry\">");
-		for( e in p ) {
-			switch e {
-			case epackage(name,full,p):
-				if( !filtered(full.join("."),true) )
-					continue;
-				print('<li><a href="#" class="package" onclick="toggle(\''+full.join("_")+'\')">'+name+"</a><div id=\""+full.join("_")+"\" class=\"package_content\">");
-				display(p);
-				print("</div></li>");
-			case eclass(c):
-				if( c.isPrivate || c.path == "@Main" || !filtered(c.path,false) )
-					continue;
-				print("<li>"+Url.make(c.path.split(".").join("/"),"entry",c.name)+"</li>");
-			}
-		}
-		print("</ul>");
-	}
-
-	static function loadFile(file) {
-		var data = neko.io.File.getContent(Web.getCwd()+file);
-		var x = Xml.parse(data).firstChild();
-		for( c in x.elements() )
-			processClass(c);
-	}
-
-	static function print(s) {
-		Url.buffer.add(s);
-	}
-
-	static function displayHtml(html : Xml,clname : String) {
-		if( html.nodeType != Xml.Element ) {
-			print(html.toString());
-			return;
-		}
-		if( html.nodeName == "data" ) {
-			if( clname == "index" )
-				clname = null;
-			if( clname == null )
-				display(entries);
-			else {
-				clname = clname.split("/").join(".");
-				var c = findEntry(entries,clname.split("."));
-				if( c == null )
-					throw ("Class not found : "+clname);
-				print(Url.make(Url.index,"index","Index"));
-				print(c.toString());
-				print(Url.make(Url.index,"index","Index"));
-			}
-			return;
-		}
-
-		if( !html.iterator().hasNext() ) {
-			print(html.toString());
-			return;
-		}
-		print("<");
-		print(html.nodeName);
-		for( k in html.attributes() )
-			print(" "+k+"=\""+html.get(k)+"\"");
-		print(">");
-		for( c in html )
-			displayHtml(c,clname);
-		print("</"+html.nodeName+">");
-	}
-
-	static var default_template = "<html><body><data/></body></html>";
-	static var filters = new List();
-
-	static function filtered(name,pack) {
-		if( pack && name == "Remoting" )
-			return false;
-		if( filters.isEmpty() )
-			return true;
-		for( x in filters )
-			if( StringTools.startsWith(name,x) )
-				return true;
-		return false;
-	}
-
-	static function save(html,clname,file) {
-		Url.buffer = new StringBuf();
-		displayHtml(html,clname);
-		var f = neko.io.File.write(file,false);
-		f.write(Url.buffer.toString());
+	static function save(html : HtmlPrinter,x,file) {
+		var f = neko.io.File.write(file,true);
+		html.output = f.write;
+		html.process(x);
 		f.close();
 		neko.Lib.print(".");
 	}
 
-	static function generateEntry(html,e,path) {
+	static function generateEntry(html : HtmlPrinter,e,path) {
 		switch( e ) {
-		case eclass(c):
-			if( !filtered(c.path,false) )
+		case TPackage(name,full,entries):
+			if( html.filtered(full,true) )
 				return;
-			save(html,c.path,path+c.name+".html");
-		case epackage(name,full,entries):
-			if( !filtered(full.join("."),true) )
-				return;
-			var old = Url.base;
-			Url.base = "../"+Url.base;
+			var old = html.baseUrl;
+			html.baseUrl = "../"+html.baseUrl;
 			path += name + "/";
 			try neko.FileSystem.createDirectory(path) catch( e : Dynamic ) { }
 			for( e in entries )
 				generateEntry(html,e,path);
-			Url.base = old;
+			html.baseUrl = old;
+		default:
+			var inf = TypeApi.typeInfos(e);
+			if( html.filtered(inf.path,false) )
+				return;
+			var pack = inf.path.split(".");
+			var name = pack.pop();
+			save(html,e,path+name+".html");
 		}
 	}
 
-	static function generateAll(html) {
-		Url.extension = ".html";
-		Url.base = "content/";
-		Url.index = "index";
-		save(html,null,"index.html");
-		Url.base = "";
-		Url.index = "../index";
+	static function generateAll(filters : List<String>) {
+		var html = new HtmlPrinter("content/",".html","../index");
+		for( f in filters )
+			html.addFilter(f);
+		save(html,TPackage("root","root",parser.root),"index.html");
+		html.baseUrl = "";
 		try neko.FileSystem.createDirectory("content") catch( e : Dynamic ) { }
-		for( e in entries )
+		for( e in parser.root )
 			generateEntry(html,e,"content/");
 	}
 
+	static function findClass( t : TypeRoot, path : Array<String>, pos : Int ) {
+		var name = path[pos];
+		var pack = (pos != path.length - 1);
+		for( c in t )
+			switch( c ) {
+			case TPackage(pname,_,subs):
+				if( name == pname )
+					return if( pack ) findClass(subs,path,pos+1) else c;
+			default:
+				if( pack ) continue;
+				var inf = TypeApi.typeInfos(c);
+				if( inf.path == path.join(".") )
+					return c;
+			}
+		return null;
+	}
+	
 	public static function main() {
-		var hdata =
-			try
-				neko.io.File.getContent(Web.getCwd()+"template.xml")
-			catch( e : Dynamic ) try {
-				var p = ~/[\/\\]/g.split(neko.Sys.executablePath());
-				p.pop();
-				neko.io.File.getContent(p.join("/")+"/std/tools/template.xml");
-			} catch( e : Dynamic )
-				default_template;
-		var html = Xml.parse(hdata).firstChild();
 		if( neko.Web.isModNeko ) {
-			var baseDir = "../data/media/";
-			Url.base = "/api/";
-			Url.index = "";
-			loadFile(baseDir+"flash.xml");
-			loadFile(baseDir+"neko.xml");
-			loadFile(baseDir+"js.xml");
-			sortEntries(entries);
-
-			var h = Web.getParams();
+			var h = neko.Web.getParams();
+			var dataFile = neko.Web.getCwd()+".data";
+			var data : TypeRoot = try neko.Lib.unserialize(neko.io.File.getContent(dataFile)) catch( e : Dynamic ) null;
+			if( h.get("reload") != null || data == null ) {
+				var baseDir = "../data/media/";
+				loadFile(baseDir+"flash.xml","flash");
+				loadFile(baseDir+"neko.xml","neko");
+				loadFile(baseDir+"js.xml","js");
+				parser.sort();
+				data = parser.root;
+				var str = neko.Lib.serialize(data);
+				var f = neko.io.File.write(dataFile,true);
+				f.write(str);
+				f.close();
+			}
+			var html = new HtmlPrinter("/api/","","");			
 			var clname = h.get("class");
-			Url.buffer = new StringBuf();
-			displayHtml(html,clname);
-			Lib.print(Url.buffer.toString());
+			if( clname == "index" )
+				clname = null;
+			if( clname == null )
+				html.process(TPackage("root","root",data));
+			else {
+				var clpath = clname.split("/").join(".").split(".");
+				var f = findClass(data,clpath,0);
+				if( f == null )
+					throw "Class not found : "+clpath.join(".");
+				html.process(f);
+			}
 		} else {
 			var filter = false;
+			var filters = new List();
 			for( x in neko.Sys.args() ) {
 				if( x == "-f" )
 					filter = true;
 				else if( filter ) {
 					filters.add(x);
 					filter = false;
-				} else
-					loadFile(x);
+				} else {
+					var f = x.split(";");
+					loadFile(f[0],f[1]);
+				}
+			}
+			parser.sort();
+			if( parser.root.length == 0 ) {
+				neko.Lib.println("Haxe Doc Generator 2.0 - (c)2006 Motion-Twin");
+				neko.Lib.println(" Usage : haxedoc [xml files] [-f filter]");
+				neko.Sys.exit(1);
 			}
-			sortEntries(entries);
-			generateAll(html);
+			generateAll(filters);
 		}
 	}
 

+ 10 - 3
std/tools/haxedoc/Type.hx

@@ -76,13 +76,13 @@ typedef Typedef = {> TypeInfos,
 }
 
 enum TypeTree {
-	TPackage( name : String, subs : List<TypeTree> );
+	TPackage( name : String, full : String, subs : Array<TypeTree> );
 	TClassdecl( c : Class );
 	TEnumdecl( e : Enum );
 	TTypedecl( t : Typedef );
 }
 
-typedef TypeRoot = List<TypeTree>
+typedef TypeRoot = Array<TypeTree>
 
 class TypeApi {
 	
@@ -92,11 +92,18 @@ class TypeApi {
 		case TClassdecl(c): inf = c;
 		case TEnumdecl(e): inf = e;
 		case TTypedecl(t): inf = t;
-		case TPackage(_,_): throw "Unexpected Package";
+		case TPackage(_,_,_): throw "Unexpected Package";
 		}
 		return inf;
 	}
 	
+	public static function isVar( t : Type ) {
+		return switch( t ) {
+		case TFunction(_,_): false;
+		default: true;
+		}
+	}
+	
 	static function leq<T>( f : T -> T -> Bool, l1 : List<T>, l2 : List<T> ) {
 		var it = l2.iterator();
 		for( e1 in l1 ) {

+ 84 - 34
std/tools/haxedoc/XmlParser.hx

@@ -5,23 +5,64 @@ import haxe.xml.Fast;
 class XmlParser {
 	
 	public var root : TypeRoot;
-	var defplat : Platforms;
+	var curplatform : String;
 	
 	public function new() {
-		root = new List();
+		root = new Array();
 	}
 	
-	public function process( x : Xml, platform : String ) {
-		defplat = new List();
-		defplat.add(platform);		
-		xroot(new Fast(x),platform);
+	public function sort( ?l ) {
+		if( l == null ) l = root;
+		l.sort(function(e1,e2) {
+			var n1 = switch e1 {
+				case TPackage(p,_,_) : " "+p;
+				default: TypeApi.typeInfos(e1).path;
+			};
+			var n2 = switch e2 {
+				case TPackage(p,_,_) : " "+p;
+				default: TypeApi.typeInfos(e2).path;
+			};
+			if( n1 > n2 )
+				return 1;
+			return -1;
+		});
+		for( x in l )
+			switch( x ) {
+			case TPackage(_,_,l): sort(l);
+			case TClassdecl(c):
+				c.fields = sortFields(c.fields);
+				c.statics = sortFields(c.statics);
+			case TEnumdecl(e):
+			case TTypedecl(_):
+			}
+	}
+	
+	function sortFields(fl) {
+		var a = Lambda.array(fl.iterator());
+		a.sort(function(f1 : ClassField,f2 : ClassField) {
+			var v1 = TypeApi.isVar(f1.type);
+			var v2 = TypeApi.isVar(f2.type);
+			if( v1 && !v2 )
+				return -1;
+			if( v2 && !v1 )
+				return 1;
+			if( f1.name > f2.name )
+				return 1;
+			return -1;
+		});
+		return Lambda.list(a.iterator());		
 	}
 	
-	function mergeClasses( c : Class, c2 : Class, platform ) {		
+	public function process( x : Xml, platform ) {
+		curplatform = platform;
+		xroot(new Fast(x));
+	}
+	
+	function mergeClasses( c : Class, c2 : Class ) {		
 		// todo : compare supers & interfaces		
 		if( c.isInterface != c2.isInterface || c.isExtern != c2.isExtern )
 			return false;
-		c.platforms.add(platform);
+		c.platforms.add(curplatform);
 		
 		for( f2 in c2.fields ) {
 			var found = null;
@@ -33,7 +74,7 @@ class XmlParser {
 			if( found == null )
 				c.fields.add(f2);
 			else
-				found.platforms.add(platform);
+				found.platforms.add(curplatform);
 		}
 		for( f2 in c2.statics ) {
 			var found = null;
@@ -45,15 +86,15 @@ class XmlParser {
 			if( found == null )
 				c.statics.add(f2);
 			else
-				found.platforms.add(platform);
+				found.platforms.add(curplatform);
 		}
 		return true;
 	}
 
-	function mergeEnums( e : Enum, e2 : Enum, platform ) {
+	function mergeEnums( e : Enum, e2 : Enum ) {
 		if( e.isExtern != e2.isExtern )
 			return false;
-		e.platforms.add(platform);
+		e.platforms.add(curplatform);
 		for( c2 in e2.constructors ) {
 			var found = null;
 			for( c in e.constructors )
@@ -63,28 +104,29 @@ class XmlParser {
 				}
 			if( found == null )
 				return false; // don't allow by-platform constructor ?
-			found.platforms.add(platform);
+			found.platforms.add(curplatform);
 		}
 		return true;
 	}
 
-	function mergeTypedefs( t : Typedef, t2 : Typedef, platform ) {
+	function mergeTypedefs( t : Typedef, t2 : Typedef ) {
 		if( !TypeApi.typeEq(t.type,t2.type) )
 			return false;
-		t.platforms.add(platform);
+		t.platforms.add(curplatform);
 		return true;
 	}
 	
-	function merge( t : TypeTree, platform ) {		
+	function merge( t : TypeTree ) {
 		var inf = TypeApi.typeInfos(t);
 		var pack = inf.path.split(".");
 		var cur = root;
+		var curpack = new Array();
 		pack.pop();		
 		for( p in pack ) {
 			var found = false;
 			for( pk in cur )
 				switch( pk ) {
-				case TPackage(pname,subs):
+				case TPackage(pname,_,subs):
 					if( pname == p ) {
 						found = true;
 						cur = subs;
@@ -92,9 +134,10 @@ class XmlParser {
 					}
 				default:
 				}
+			curpack.push(p);
 			if( !found ) {
-				var pk = new List();
-				cur.add(TPackage(p,pk));
+				var pk = new Array();
+				cur.push(TPackage(p,curpack.join("."),pk));
 				cur = pk;
 			}
 		}
@@ -108,31 +151,31 @@ class XmlParser {
 					case TClassdecl(c):
 						switch( t ) {
 						case TClassdecl(c2):
-							if( mergeClasses(c,c2,platform) )
+							if( mergeClasses(c,c2) )
 								return;
 						default:
 						}
 					case TEnumdecl(e):
 						switch( t ) {
 						case TEnumdecl(e2):
-							if( mergeEnums(e,e2,platform) )
+							if( mergeEnums(e,e2) )
 								return;
 						default:
 						}
 					case TTypedecl(td):
 						switch( t ) {
 						case TTypedecl(td2):
-							if( mergeTypedefs(td,td2,platform) )
+							if( mergeTypedefs(td,td2) )
 								return;
 						default:
 						}
-					case TPackage(_,_):
+					case TPackage(_,_,_):
 					}
 				// we already have a mapping, but which is incompatible
-				throw "Incompatibilities between "+tinf.path+" in "+tinf.platforms.join(",")+" and "+platform;
+				throw "Incompatibilities between "+tinf.path+" in "+tinf.platforms.join(",")+" and "+curplatform;
 			}
 		}
-		cur.add(t);
+		cur.push(t);
 	}
 	
 	function mkPath( p : String ) : Path {
@@ -156,18 +199,18 @@ class XmlParser {
 		return throw "Invalid "+c.name;
 	}
 	
-	function xroot( x : Fast, platform ) {		
+	function xroot( x : Fast ) {		
 		for( c in x.elements )
 			switch( c.name ) {
 			case "class":
 				var cl = xclass(c);
-				merge(TClassdecl(cl),platform);
+				merge(TClassdecl(cl));
 			case "enum":
 				var e = xenum(c);
-				merge(TEnumdecl(e),platform);
+				merge(TEnumdecl(e));
 			case "typedef":
 				var td = xtypedef(c);
-				merge(TTypedecl(td),platform);
+				merge(TTypedecl(td));
 			default:
 				xerror(c);
 			}
@@ -216,7 +259,7 @@ class XmlParser {
 			fields : fields,
 			statics : statics,
 			dynamic : dynamic,
-			platforms : defplat,
+			platforms : defplat(),
 		};
 	}
 	
@@ -237,7 +280,7 @@ class XmlParser {
 			get : if( x.has.get ) mkRights(x.att.get) else RNormal,
 			set : if( x.has.set ) mkRights(x.att.set) else RNormal,
 			params : if( x.has.params ) mkTypeParams(x.att.params) else null,
-			platforms : defplat,
+			platforms : defplat(),
 		};
 	}
 
@@ -257,7 +300,7 @@ class XmlParser {
 			isExtern : x.x.exists("extern"),
 			params : mkTypeParams(x.att.params),
 			constructors : cl,
-			platforms : defplat,
+			platforms : defplat(),
 		};
 	}
 	
@@ -285,7 +328,7 @@ class XmlParser {
 			name : x.name,
 			args : args,
 			doc : if( xdoc == null ) null else new Fast(xdoc).innerData,
-			platforms : defplat,
+			platforms : defplat(),
 		};
 	}
 
@@ -304,7 +347,7 @@ class XmlParser {
 			isPrivate : x.x.exists("private"),
 			params : mkTypeParams(x.att.params),
 			type : t,
-			platforms : defplat,
+			platforms : defplat(),
 		};
 	}
 	
@@ -360,5 +403,12 @@ class XmlParser {
 			p.add(xtype(c));
 		return p;
 	}
+	
+	function defplat() {
+		var l = new List();
+		if( curplatform != null )
+			l.add(curplatform);
+		return l;
+	}
 
 }

+ 3 - 4
std/tools/haxedoc/haxedoc.hxml

@@ -1,6 +1,5 @@
-#
-# compile DocView service
-#
--main tools.haxedoc.Main
+# haxedoc
 -neko haxedoc.n
+-main tools.haxedoc.Main
 -cmd nekotools boot haxedoc.n
+tools.haxedoc.XmlParser

+ 1 - 0
std/tools/haxedoc/haxedoc.hxp

@@ -2,6 +2,7 @@
   <output name="haxedoc" mode="neko" out="haxedoc.n" class="tools.haxedoc.Main" lib="" cmd="" main="True" debug="False">-cmd nekotools boot haxedoc.n
 tools.haxedoc.XmlParser</output>
   <files path="/">
+    <file path="HtmlPrinter.hx" />
     <file path="Main.hx" />
     <file path="Type.hx" />
     <file path="XmlParser.hx" />

+ 8 - 5
std/tools/haxedoc/template.xml

@@ -40,7 +40,7 @@ body {
 ul.entry {
 	list-style-type: circle;
 	font-weight : bold;
-	padding-left : 30px;
+	margin-left : 30px;
 }
 
 .package_content {
@@ -66,6 +66,10 @@ a.package {
 	margin-left : 10px;
 }
 
+.kwd {
+	color : #00A;
+}
+
 .classname {
 	font-size : 30;
 	font-weight : bold;
@@ -80,8 +84,7 @@ a.package {
 	padding : 5 5 5 5;
 }
 
-.importmod {
-	margin-top : -20px;
+.importmod, .extends, .implements, .typedef, .platforms {
 	margin-left: 20px;
 	color : #777;
 }
@@ -89,14 +92,14 @@ a.package {
 dd {
 	margin : 0 20 20 40;
 	font-size : 12pt;
+	color : #444;
 }
 
 dt {
 	margin-left : 20px;
-	margin-bottom : 10px;
+	margin-bottom : 5px;
 	text-align : left;
 	font-size : 10pt;
-	font-weight : bold;
 	font-family: Courier New, monospace;
 }