Browse Source

added tags support and search

Nicolas Cannasse 16 years ago
parent
commit
bba6836971

+ 7 - 0
std/tools/haxelib/Datas.hx

@@ -24,6 +24,7 @@ typedef ProjectInfos = {
 	var license : String;
 	var license : String;
 	var curversion : String;
 	var curversion : String;
 	var versions : Array<VersionInfos>;
 	var versions : Array<VersionInfos>;
+	var tags : List<String>;
 }
 }
 
 
 typedef XmlInfos = {
 typedef XmlInfos = {
@@ -34,6 +35,7 @@ typedef XmlInfos = {
 	var version : String;
 	var version : String;
 	var versionComments : String;
 	var versionComments : String;
 	var developers : List<String>;
 	var developers : List<String>;
+	var tags : List<String>;
 	var dependencies : List<{ project : String, version : String }>;
 	var dependencies : List<{ project : String, version : String }>;
 }
 }
 
 
@@ -98,6 +100,7 @@ class Datas {
 			[ sname, Att("url"), Att("license",FEnum(LICENSES)) ],
 			[ sname, Att("url"), Att("license",FEnum(LICENSES)) ],
 			RList([
 			RList([
 				RMulti( RNode("user",[sname]), true ),
 				RMulti( RNode("user",[sname]), true ),
+				RMulti( RNode("tag",[Att("v",FReg(alphanum))]) ),
 				RNode("description",[],RData()),
 				RNode("description",[],RData()),
 				RNode("version",[sname],RData()),
 				RNode("version",[sname],RData()),
 				RMulti(	RNode("depends",[sname,Att("version",FReg(alphanum),"")]) ),
 				RMulti(	RNode("depends",[sname,Att("version",FReg(alphanum),"")]) ),
@@ -110,6 +113,9 @@ class Datas {
 		var project = p.att.name;
 		var project = p.att.name;
 		if( project.length < 3 )
 		if( project.length < 3 )
 			throw "Project name must contain at least 3 characters";
 			throw "Project name must contain at least 3 characters";
+		var tags = new List();
+		for( t in p.nodes.tag )
+			tags.add(t.att.v.toLowerCase());
 		var devs = new List();
 		var devs = new List();
 		for( d in p.nodes.user )
 		for( d in p.nodes.user )
 			devs.add(d.att.name);
 			devs.add(d.att.name);
@@ -123,6 +129,7 @@ class Datas {
 			version : p.node.version.att.name,
 			version : p.node.version.att.name,
 			versionComments : p.node.version.innerData,
 			versionComments : p.node.version.innerData,
 			license : p.att.license,
 			license : p.att.license,
+			tags : tags,
 			developers : devs,
 			developers : devs,
 			dependencies : deps,
 			dependencies : deps,
 		}
 		}

+ 26 - 3
std/tools/haxelib/Main.hx

@@ -124,6 +124,10 @@ class Main {
 		addCommand("run",run,"run the specified project with parameters",false);
 		addCommand("run",run,"run the specified project with parameters",false);
 		addCommand("test",test,"install the specified package localy",false);
 		addCommand("test",test,"install the specified package localy",false);
 		addCommand("dev",dev,"set the development directory for a given project",false);
 		addCommand("dev",dev,"set the development directory for a given project",false);
+		initSite();
+	}
+
+	function initSite() {
 		siteUrl = "http://"+SERVER.host+":"+SERVER.port+"/"+SERVER.dir;
 		siteUrl = "http://"+SERVER.host+":"+SERVER.port+"/"+SERVER.dir;
 		site = new SiteProxy(haxe.remoting.HttpConnection.urlConnect(siteUrl+SERVER.url).api);
 		site = new SiteProxy(haxe.remoting.HttpConnection.urlConnect(siteUrl+SERVER.url).api);
 	}
 	}
@@ -179,9 +183,27 @@ class Main {
 	function process() {
 	function process() {
 		var debug = false;
 		var debug = false;
 		argcur = 0;
 		argcur = 0;
-		if( args[argcur] == "-debug" ) {
-			argcur++;
-			debug = true;
+		while( true ) {
+			var a = args[argcur++];
+			if( a == null )
+				break;
+			switch( a ) {
+			case "-debug":
+				debug = true;
+			case "-R":
+				var path = args[argcur++];
+				var r = ~/^(http:\/\/)?([^:\/]+)(:[0-9]+)?\/?(.*)$/;
+				if( !r.match(path) )
+					throw "Invalid repository format '"+path+"'";
+				SERVER.host = r.matched(2);
+				if( r.matched(3) != null )
+					SERVER.port = Std.parseInt(r.matched(3).substr(1));
+				SERVER.dir = r.matched(4);
+				initSite();
+			default:
+				argcur--;
+				break;
+			}
 		}
 		}
 		var cmd = args[argcur++];
 		var cmd = args[argcur++];
 		if( cmd == null )
 		if( cmd == null )
@@ -231,6 +253,7 @@ class Main {
 		var prj = param("Project name");
 		var prj = param("Project name");
 		var inf = site.infos(prj);
 		var inf = site.infos(prj);
 		print("Name: "+inf.name);
 		print("Name: "+inf.name);
+		print("Tags: "+inf.tags.join(", "));
 		print("Desc: "+inf.desc);
 		print("Desc: "+inf.desc);
 		print("Website: "+inf.website);
 		print("Website: "+inf.website);
 		print("License: "+inf.license);
 		print("License: "+inf.license);

+ 25 - 4
std/tools/haxelib/Site.hx

@@ -74,9 +74,9 @@ class Site {
 		if( uri[0] == "" )
 		if( uri[0] == "" )
 			uri.shift();
 			uri.shift();
 		var act = uri.shift();
 		var act = uri.shift();
-		if( act == null || act == "" )
+		if( act == null || act == "" || act == "index.n" )
 			act = "index";
 			act = "index";
-		ctx.projects = Project.manager.allByName();
+		ctx.menuTags = Tag.manager.topTags(10);
 		switch( act ) {
 		switch( act ) {
 		case "p":
 		case "p":
 			var name = uri.shift();
 			var name = uri.shift();
@@ -89,6 +89,8 @@ class Site {
 			ctx.owner = p.owner;
 			ctx.owner = p.owner;
 			ctx.version = p.version;
 			ctx.version = p.version;
 			ctx.versions = Version.manager.byProject(p);
 			ctx.versions = Version.manager.byProject(p);
+			var tags = Tag.manager.search({ project : p.id });
+			if( !tags.isEmpty() ) ctx.tags = tags;
 		case "u":
 		case "u":
 			var name = uri.shift();
 			var name = uri.shift();
 			var u = User.manager.search({ name : name }).first();
 			var u = User.manager.search({ name : name }).first();
@@ -98,18 +100,37 @@ class Site {
 			}
 			}
 			ctx.u = u;
 			ctx.u = u;
 			ctx.uprojects = Developer.manager.search({ user : u.id }).map(function(d:Developer) { return d.project; });
 			ctx.uprojects = Developer.manager.search({ user : u.id }).map(function(d:Developer) { return d.project; });
+		case "t":
+			var tag = uri.shift();
+			ctx.tag = StringTools.htmlEscape(tag);
+			ctx.tprojects = Tag.manager.search({ tag : tag }).map(function(t) return t.project);
 		case "index":
 		case "index":
 			var vl = Version.manager.latest(10);
 			var vl = Version.manager.latest(10);
 			for( v in vl ) {
 			for( v in vl ) {
-				var p = v.project;
+				var p = v.project; // fetch
 			}
 			}
 			ctx.versions = vl;
 			ctx.versions = vl;
+		case "all":
+			ctx.projects = Project.manager.allByName();
+		case "search":
+			var v = neko.Web.getParams().get("v");
+			var p = Project.manager.search({ name : v }).first();
+			if( p != null ) {
+				neko.Web.redirect("/p/"+p.name);
+				return false;
+			}
+			if( Tag.manager.count({ tag : v }) > 0 ) {
+				neko.Web.redirect("/t/"+v);
+				return false;
+			}
+			ctx.projects = Project.manager.containing(v).map(function(p) return Project.manager.get(p.id));
+			ctx.act_all = true;
+			ctx.search = StringTools.htmlEscape(v);
 		case "rss":
 		case "rss":
 			neko.Web.setHeader("Content-Type", "text/xml; charset=UTF-8");
 			neko.Web.setHeader("Content-Type", "text/xml; charset=UTF-8");
 			neko.Lib.println('<?xml version="1.0" encoding="UTF-8"?>');
 			neko.Lib.println('<?xml version="1.0" encoding="UTF-8"?>');
 			neko.Lib.print(buildRss().toString());
 			neko.Lib.print(buildRss().toString());
 			return false;
 			return false;
-
 		default:
 		default:
 			ctx.error = "Unknown action : "+act;
 			ctx.error = "Unknown action : "+act;
 			return true;
 			return true;

+ 25 - 2
std/tools/haxelib/SiteApi.hx

@@ -10,7 +10,7 @@ class SiteApi {
 		this.db = db;
 		this.db = db;
 	}
 	}
 
 
-	public function search( word : String ) : List<{ name : String }> {
+	public function search( word : String ) : List<{ id : Int, name : String }> {
 		return Project.manager.containing(word);
 		return Project.manager.containing(word);
 	}
 	}
 
 
@@ -30,6 +30,7 @@ class SiteApi {
 			owner : p.owner.name,
 			owner : p.owner.name,
 			website : p.website,
 			website : p.website,
 			license : p.license,
 			license : p.license,
+			tags : Tag.manager.search({ project : p.id }).map(function(t) return t.tag),
 		};
 		};
 	}
 	}
 
 
@@ -105,6 +106,9 @@ class SiteApi {
 			return u;
 			return u;
 		});
 		});
 
 
+		var tags = Lambda.array(infos.tags);
+		tags.sort(Reflect.compare);
+
 		var p = Project.manager.search({ name : infos.project }).first();
 		var p = Project.manager.search({ name : infos.project }).first();
 
 
 		// create project if needed
 		// create project if needed
@@ -122,6 +126,12 @@ class SiteApi {
 				d.project = p;
 				d.project = p;
 				d.insert();
 				d.insert();
 			}
 			}
+			for( tag in tags ) {
+				var t = new Tag();
+				t.tag = tag;
+				t.project = p;
+				t.insert();
+			}
 		}
 		}
 
 
 		// check submit rights
 		// check submit rights
@@ -135,9 +145,12 @@ class SiteApi {
 		if( !isdev )
 		if( !isdev )
 			throw "You are not a developer of this project";
 			throw "You are not a developer of this project";
 
 
+		var otags = Tag.manager.search({ project : p.id });
+		var curtags = otags.map(function(t) return t.tag).join(":");
+
 		// update public infos
 		// update public infos
 		var update = false;
 		var update = false;
-		if( infos.desc != p.description || p.website != infos.website || pdevs.length != devs.length ) {
+		if( infos.desc != p.description || p.website != infos.website || pdevs.length != devs.length || tags.join(":") != curtags ) {
 			if( u.id != p.owner.id )
 			if( u.id != p.owner.id )
 				throw "Only project owner can modify project infos";
 				throw "Only project owner can modify project infos";
 			p.description = infos.desc;
 			p.description = infos.desc;
@@ -153,6 +166,16 @@ class SiteApi {
 					d.insert();
 					d.insert();
 				}
 				}
 			}
 			}
+			if( tags.join(":") != curtags ) {
+				for( t in otags )
+					t.delete();
+				for( tag in tags ) {
+					var t = new Tag();
+					t.tag = tag;
+					t.project = p;
+					t.insert();
+				}
+			}
 			update = true;
 			update = true;
 			neko.FileSystem.deleteFile(path);
 			neko.FileSystem.deleteFile(path);
 			return "Project infos updated : submit one more time to send a new version";
 			return "Project infos updated : submit one more time to send a new version";

+ 36 - 2
std/tools/haxelib/SiteDb.hx

@@ -33,6 +33,22 @@ class Project extends neko.db.Object {
 
 
 }
 }
 
 
+class Tag extends neko.db.Object {
+
+	static function RELATIONS() {
+		return [
+			{ key : "project", prop : "project", manager : Project.manager },
+		];
+	}
+
+	public static var manager = new TagManager(Tag);
+
+	public var id : Int;
+	public var tag : String;
+	public var project(dynamic,dynamic) : Project;
+
+}
+
 class Version extends neko.db.Object {
 class Version extends neko.db.Object {
 
 
 	static function RELATIONS() {
 	static function RELATIONS() {
@@ -69,9 +85,9 @@ class Developer extends neko.db.Object {
 
 
 class ProjectManager extends neko.db.Manager<Project> {
 class ProjectManager extends neko.db.Manager<Project> {
 
 
-	public function containing( word ) {
+	public function containing( word ) : List<{ id : Int, name : String }> {
 		word = quote("%"+word+"%");
 		word = quote("%"+word+"%");
-		return results("SELECT name FROM Project WHERE name LIKE "+word+" OR description LIKE "+word);
+		return results("SELECT id, name FROM Project WHERE name LIKE "+word+" OR description LIKE "+word);
 	}
 	}
 
 
 	public function allByName() {
 	public function allByName() {
@@ -92,6 +108,14 @@ class VersionManager extends neko.db.Manager<Version> {
 
 
 }
 }
 
 
+class TagManager extends neko.db.Manager<Tag> {
+
+	public function topTags( n : Int ) {
+		return results("SELECT tag, COUNT(*) as count FROM Tag GROUP BY tag ORDER BY count DESC LIMIT "+n);
+	}
+
+}
+
 class SiteDb {
 class SiteDb {
 
 
 	public static function create( db : neko.db.Connection ) {
 	public static function create( db : neko.db.Connection ) {
@@ -135,5 +159,15 @@ class SiteDb {
 				project INTEGER NOT NULL
 				project INTEGER NOT NULL
 			)
 			)
 		");
 		");
+		db.request("DROP TABLE IF EXISTS Tag");
+		db.request("
+			CREATE TABLE Tag (
+				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+				tag VARCHAR(32) NOT NULL,
+				project INTEGER NOT NULL
+			)
+		");
+		db.request("DROP INDEX IF EXISTS TagSearch");
+		db.request("CREATE INDEX TagSearch ON Tag(tag)");
 	}
 	}
 }
 }

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

@@ -7,5 +7,6 @@
     <file path="Site.hx" />
     <file path="Site.hx" />
     <file path="SiteApi.hx" />
     <file path="SiteApi.hx" />
     <file path="SiteDb.hx" />
     <file path="SiteDb.hx" />
+    <file path="website.mtt" />
   </files>
   </files>
 </haxe>
 </haxe>

+ 55 - 3
std/tools/haxelib/website.mtt

@@ -125,6 +125,18 @@ h1 a, h1 a:hover, h1 a:visited {
 	float : left;
 	float : left;
 }
 }
 
 
+.tags a {
+	margin-right : 5px;
+}
+
+form {
+	margin-left : 5px;
+	margin-top : 5px;
+}
+
+input {
+	width : 80px;
+}
 
 
 </style>
 </style>
 </head>
 </head>
@@ -136,10 +148,14 @@ h1 a, h1 a:hover, h1 a:visited {
 <h1><a href="/">lib.haxe.org</a></h1>
 <h1><a href="/">lib.haxe.org</a></h1>
 
 
 <div class="menu">
 <div class="menu">
-	<div class="title">Projects :</div>
+	<div class="title">Search :</div>
+	<form action="/search" method="POST">
+		<input name="v"/>
+	</form>
+	<div class="title">Top tags :</div>
 	<ul>
 	<ul>
-	::foreach projects::
-	<li><a href="/p/::name::">::name::</a></li>
+	::foreach menuTags::
+	<li><a href="/t/::tag::">::tag::</a> (::count::)</li>
 	::end::
 	::end::
 	</ul>
 	</ul>
 </div>
 </div>
@@ -178,6 +194,7 @@ h1 a, h1 a:hover, h1 a:visited {
 </ul>
 </ul>
 </div>
 </div>
 
 
+<a href="/all">Browse Projects</a>
 
 
 ::elseif act_p::
 ::elseif act_p::
 
 
@@ -185,6 +202,7 @@ h1 a, h1 a:hover, h1 a:visited {
 
 
 <div class="pinfos">
 <div class="pinfos">
 	<div class="description">::(p.description)::</div>
 	<div class="description">::(p.description)::</div>
+	::if tags::<div class="tags"><div class="label">Tags</div> ::foreach tags::<a href="/t/::tag::">::tag::</a>::end::</div>::end::
 	<div class="url"><div class="label">Website</div> <a href="::(p.website)::">::(p.website)::</a></div>
 	<div class="url"><div class="label">Website</div> <a href="::(p.website)::">::(p.website)::</a></div>
 	<div class="version"><div class="label">Version</div> ::(version.name)::</div>
 	<div class="version"><div class="label">Version</div> ::(version.name)::</div>
 	<div class="owner"><div class="label">Owner</div> <a href="/u/::(owner.name)::">::(owner.name)::</a></div>
 	<div class="owner"><div class="label">Owner</div> <a href="/u/::(owner.name)::">::(owner.name)::</a></div>
@@ -226,6 +244,40 @@ h1 a, h1 a:hover, h1 a:visited {
 </ul>
 </ul>
 </div>
 </div>
 
 
+::elseif act_t::
+
+<h2>Tag ::tag::</h2>
+
+<p>
+	Here's the list of projects using this tag :
+</p>
+
+<div class="projects">
+<ul>
+::foreach tprojects::
+	<li>
+		<a href="/p/::name::">::name::</a>
+		<div class="description">::description::</div>
+	</li>
+::end::
+</ul>
+</div>
+
+::elseif act_all::
+
+<h2>::if search::Search Results for '::search::'::else::All Projects::end:::</h2>
+
+<div class="projects">
+<ul>
+::foreach projects::
+	<li>
+		<a href="/p/::name::">::name::</a>
+		<div class="description">::description::</div>
+	</li>
+::end::
+</ul>
+</div>
+
 ::else::
 ::else::
 
 
 <p>
 <p>