Main.hx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. package tools.haxelib;
  2. class SiteProxy extends haxe.remoting.Proxy<tools.haxelib.SiteApi> {
  3. }
  4. class Progress extends neko.io.Output {
  5. var o : neko.io.Output;
  6. var cur : Int;
  7. var max : Int;
  8. var start : Float;
  9. public function new(o) {
  10. this.o = o;
  11. cur = 0;
  12. start = haxe.Timer.stamp();
  13. }
  14. function bytes(n) {
  15. cur += n;
  16. if( max == null )
  17. neko.Lib.print(cur+" bytes\r");
  18. else
  19. neko.Lib.print(cur+"/"+max+" ("+Std.int((cur*100.0)/max)+"%)\r");
  20. }
  21. public override function writeChar(c) {
  22. o.writeChar(c);
  23. bytes(1);
  24. }
  25. public override function writeBytes(s,p,l) {
  26. var r = o.writeBytes(s,p,l);
  27. bytes(r);
  28. return r;
  29. }
  30. public override function close() {
  31. super.close();
  32. o.close();
  33. var time = haxe.Timer.stamp() - start;
  34. var speed = (cur / time) / 1024;
  35. time = Std.int(time * 10) / 10;
  36. speed = Std.int(speed * 10) / 10;
  37. neko.Lib.print("Download complete : "+cur+" bytes in "+time+"s ("+speed+"KB/s)\n");
  38. }
  39. public override function prepare(m) {
  40. max = m;
  41. }
  42. }
  43. class Main {
  44. static var VERSION = 100;
  45. static var REPNAME = "lib";
  46. static var SERVER = {
  47. host : "lib.haxe.org",
  48. port : 80,
  49. dir : "",
  50. url : "index.n"
  51. };
  52. var argcur : Int;
  53. var args : Array<String>;
  54. var commands : List<{ name : String, doc : String, f : Void -> Void }>;
  55. var siteUrl : String;
  56. var site : SiteProxy;
  57. function new() {
  58. args = neko.Sys.args();
  59. commands = new List();
  60. addCommand("install",install,"install a given project");
  61. addCommand("list",list,"list all installed projects");
  62. addCommand("upgrade",upgrade,"upgrade all installed projects");
  63. addCommand("remove",remove,"remove a given project/version");
  64. addCommand("set",set,"set the current version for a project");
  65. addCommand("search",search,"list projects matching a word");
  66. addCommand("info",info,"list informations on a given project");
  67. addCommand("user",user,"list informations on a given user");
  68. addCommand("submit",submit,"submit or update a project package");
  69. addCommand("setup",setup,"set the haxelib repository path");
  70. addCommand("config",config,"print the repository path");
  71. addCommand("path",path,"give paths to libraries");
  72. siteUrl = "http://"+SERVER.host+":"+SERVER.port+"/"+SERVER.dir;
  73. site = new SiteProxy(haxe.remoting.Connection.urlConnect(siteUrl+SERVER.url).api);
  74. }
  75. function param( name, ?passwd ) {
  76. if( args.length > argcur )
  77. return args[argcur++];
  78. neko.Lib.print(name+" : ");
  79. if( passwd ) {
  80. var s = new StringBuf();
  81. var c;
  82. while( (c = neko.io.File.getChar(false)) != 13 )
  83. s.addChar(c);
  84. print("");
  85. return s.toString();
  86. }
  87. return neko.io.File.stdin().readLine();
  88. }
  89. function paramOpt() {
  90. if( args.length > argcur )
  91. return args[argcur++];
  92. return null;
  93. }
  94. function addCommand( name, f, doc ) {
  95. commands.add({ name : name, doc : doc, f : f });
  96. }
  97. function usage() {
  98. var vmin = Std.string(VERSION % 100);
  99. var ver = Std.int(VERSION/100) + "." + if( vmin.length == 0 ) "0"+vmin else vmin;
  100. print("Haxe Library Manager "+ver+" - (c)2006 Motion-Twin");
  101. print(" Usage : haxlib [command] [options]");
  102. print(" Commands :");
  103. for( c in commands )
  104. print(" "+c.name+" : "+c.doc);
  105. neko.Sys.exit(1);
  106. }
  107. function run() {
  108. var debug = false;
  109. argcur = 0;
  110. if( args[argcur] == "-debug" ) {
  111. argcur++;
  112. debug = true;
  113. }
  114. var cmd = args[argcur++];
  115. if( cmd == null )
  116. usage();
  117. for( c in commands )
  118. if( c.name == cmd ) {
  119. try {
  120. c.f();
  121. } catch( e : Dynamic ) {
  122. if( debug )
  123. neko.Lib.rethrow(e);
  124. print(Std.string(e));
  125. neko.Sys.exit(1);
  126. }
  127. return;
  128. }
  129. print("Unknown command "+cmd);
  130. usage();
  131. }
  132. // ---- COMMANDS --------------------
  133. function search() {
  134. var word = param("Search word");
  135. var l = site.search(word);
  136. for( s in l )
  137. print(s.name);
  138. print(l.length+" projects found");
  139. }
  140. function info() {
  141. var prj = param("Project name");
  142. var inf = site.infos(prj);
  143. print("Name: "+inf.name);
  144. print("Desc: "+inf.desc);
  145. print("Website: "+inf.website);
  146. print("License: "+inf.license);
  147. print("Owner: "+inf.owner);
  148. print("Version: "+inf.curversion);
  149. print("Releases: ");
  150. if( inf.versions.length == 0 )
  151. print(" (no version released yet)");
  152. for( v in inf.versions )
  153. print(" "+v.date+" "+v.name+" : "+v.comments);
  154. }
  155. function user() {
  156. var uname = param("User name");
  157. var inf = site.user(uname);
  158. print("Id: "+inf.name);
  159. print("Name: "+inf.fullname);
  160. print("Mail: "+inf.email);
  161. print("Projects: ");
  162. if( inf.projects.length == 0 )
  163. print(" (no projects)");
  164. for( p in inf.projects )
  165. print(" "+p);
  166. }
  167. function register(name) {
  168. print("This is your first submission as '"+name+"'");
  169. print("Please enter the following informations for registration");
  170. var email = param("Email");
  171. var fullname = param("Fullname");
  172. var pass = param("Password",true);
  173. var pass2 = param("Confirm",true);
  174. if( pass != pass2 )
  175. throw "Password does not match";
  176. pass = haxe.Md5.encode(pass);
  177. site.register(name,pass,email,fullname);
  178. return pass;
  179. }
  180. function submit() {
  181. var file = param("Package");
  182. var data = neko.io.File.getContent(file);
  183. var zip = neko.zip.File.read(new neko.io.StringInput(data));
  184. var infos = Datas.readInfos(zip);
  185. var password;
  186. site.checkOwner(infos.project,infos.owner);
  187. if( site.isNewUser(infos.owner) )
  188. password = register(infos.owner);
  189. else {
  190. password = haxe.Md5.encode(param("Password",true));
  191. if( !site.checkPassword(infos.owner,password) )
  192. throw "Invalid password for "+infos.owner;
  193. }
  194. // check dependencies validity
  195. for( d in infos.dependencies ) {
  196. var infos = site.infos(d.project);
  197. if( d.version == "" )
  198. continue;
  199. var found = false;
  200. for( v in infos.versions )
  201. if( v.name == d.version ) {
  202. found = true;
  203. break;
  204. }
  205. if( !found )
  206. throw "Project "+d.project+" does not have version "+d.version;
  207. }
  208. // query a submit id that will identify the file
  209. var id = site.getSubmitId();
  210. // directly send the file data over Http
  211. // we can't use haxe.Http because we want *sent* data progress
  212. var s = new neko.io.Socket();
  213. s.connect(neko.io.Socket.resolve(SERVER.host),SERVER.port);
  214. s.write("POST /"+SERVER.url+"?submit="+id);
  215. s.write(" HTTP/1.1\r\nHost: "+SERVER.host+"\r\n");
  216. s.write("Content-Type: application/octet-stream\r\n");
  217. s.write("Content-Length: "+data.length+"\r\n");
  218. s.write("\r\n");
  219. var pos = 0;
  220. var bufsize = 1024;
  221. print("Sending data.... ");
  222. while( pos < data.length ) {
  223. s.write(data.substr(pos,bufsize));
  224. pos += bufsize;
  225. neko.Lib.print( Std.int((pos * 100.0) / data.length) + "%\r" );
  226. }
  227. s.shutdown(false,true);
  228. s.input.readAll();
  229. s.close();
  230. // ask the server to register the sent file
  231. var msg = site.processSubmit(id,password);
  232. print(msg);
  233. }
  234. function install() {
  235. var prj = param("Project name");
  236. var inf = site.infos(prj);
  237. if( inf.curversion == null )
  238. throw "This project has not yet released a version";
  239. var reqversion = paramOpt();
  240. var version = if( reqversion != null ) reqversion else inf.curversion;
  241. var found = false;
  242. for( v in inf.versions )
  243. if( v.name == version ) {
  244. found = true;
  245. break;
  246. }
  247. if( !found )
  248. throw "No such version "+version;
  249. doInstall(inf.name,version,version == inf.curversion);
  250. }
  251. function doInstall( project, version, setcurrent ) {
  252. var rep = getRepository();
  253. // create/delete directories first
  254. var pdir = rep+Datas.safe(project);
  255. safeDir(pdir);
  256. pdir += "/";
  257. var target = pdir+Datas.safe(version);
  258. if( !safeDir(target) ) {
  259. print("You already have "+project+" version "+version+" installed");
  260. return;
  261. }
  262. target += "/";
  263. // download to temporary file
  264. var filename = Datas.fileName(project,version);
  265. var filepath = rep+filename;
  266. var out = neko.io.File.write(filepath,true);
  267. var progress = new Progress(out);
  268. var h = new haxe.Http(siteUrl+Datas.REPOSITORY+"/"+filename);
  269. h.onError = function(e) {
  270. progress.close();
  271. neko.FileSystem.deleteFile(filepath);
  272. throw e;
  273. };
  274. print("Downloading "+filename+"...");
  275. h.asyncRequest(false,progress);
  276. // read zip content
  277. var f = neko.io.File.read(filepath,true);
  278. var zip = neko.zip.File.read(f);
  279. f.close();
  280. // locate haxelib.xml base path
  281. var basepath = null;
  282. for( f in zip ) {
  283. if( StringTools.endsWith(f.fileName,Datas.XML) ) {
  284. basepath = f.fileName.substr(0,f.fileName.length - Datas.XML.length);
  285. break;
  286. }
  287. }
  288. if( basepath == null )
  289. throw "No "+Datas.XML+" found";
  290. // unzip content
  291. for( zipfile in zip ) {
  292. var n = zipfile.fileName;
  293. if( StringTools.startsWith(n,basepath) ) {
  294. // remove basepath
  295. n = n.substr(basepath.length,n.length-basepath.length);
  296. if( n.charAt(0) == "/" || n.charAt(0) == "\\" || n.split("..").length > 1 )
  297. throw "Invalid filename : "+n;
  298. var dirs = ~/[\/\\]/g.split(n);
  299. var path = "";
  300. var file = dirs.pop();
  301. for( d in dirs ) {
  302. path += d;
  303. safeDir(target+path);
  304. path += "/";
  305. }
  306. if( file == "" ) {
  307. if( path != "" ) print(" Created "+path);
  308. continue; // was just a directory
  309. }
  310. path += file;
  311. print(" Install "+path);
  312. var data = neko.zip.File.unzip(zipfile);
  313. var f = neko.io.File.write(target+path,true);
  314. f.write(data);
  315. f.close();
  316. }
  317. }
  318. // set current version
  319. if( setcurrent || !neko.FileSystem.exists(pdir+".current") ) {
  320. var f = neko.io.File.write(pdir+".current",true);
  321. f.write(version);
  322. f.close();
  323. print(" Current version is now "+version);
  324. }
  325. // end
  326. neko.FileSystem.deleteFile(filepath);
  327. print("Done");
  328. // process dependencies
  329. var infos = Datas.readInfos(zip);
  330. for( d in infos.dependencies ) {
  331. print("Installing dependency "+d.project+" "+d.version);
  332. if( d.version == "" )
  333. d.version = site.infos(d.project).curversion;
  334. doInstall(d.project,d.version,false);
  335. }
  336. }
  337. function safeDir( dir ) {
  338. if( neko.FileSystem.exists(dir) ) {
  339. if( !neko.FileSystem.isDirectory(dir) )
  340. throw ("A file is preventing "+dir+" to be created");
  341. return false;
  342. }
  343. neko.FileSystem.createDirectory(dir);
  344. return true;
  345. }
  346. function getRepository( ?setup : Bool ) {
  347. var sys = neko.Sys.systemName();
  348. if( sys == "Windows" ) {
  349. var haxepath = neko.Sys.getEnv("HAXEPATH");
  350. if( haxepath == null )
  351. throw "HAXEPATH environment variable not defined, please run haxesetup.exe first";
  352. var rep = haxepath+REPNAME;
  353. safeDir(rep);
  354. return rep+"\\";
  355. }
  356. var config = neko.Sys.getEnv("HOME")+"/.haxelib";
  357. var rep = try
  358. neko.io.File.getContent(config)
  359. catch( e : Dynamic ) try
  360. neko.io.File.getContent("/etc/.haxelib")
  361. catch( e : Dynamic )
  362. if( setup ) {
  363. if( sys == "Linux" ) "/usr/lib/haxe/"+REPNAME else "/usr/local/lib/haxe/"+REPNAME;
  364. } else
  365. throw "This is the first time you are runing haxelib. Please run haxelib setup first";
  366. if( setup ) {
  367. print("Please enter haxelib repository path with write access");
  368. print("Hit enter for default ("+rep+")");
  369. var line = param("Path");
  370. if( line != "" )
  371. rep = line;
  372. if( !neko.FileSystem.exists(rep) )
  373. neko.FileSystem.createDirectory(rep);
  374. var f = neko.io.File.write(config,true);
  375. f.write(rep);
  376. f.close();
  377. } else if( !neko.FileSystem.exists(rep) )
  378. throw "haxelib Repository "+rep+" does not exists. Please run haxelib setup again";
  379. return rep+"/";
  380. }
  381. function setup() {
  382. var path = getRepository(true);
  383. print("haxelib repository is now "+path);
  384. }
  385. function config() {
  386. print(getRepository());
  387. }
  388. function list() {
  389. var rep = getRepository();
  390. for( p in neko.FileSystem.readDirectory(rep) ) {
  391. var versions = neko.FileSystem.readDirectory(rep+p);
  392. var current = neko.io.File.getContent(rep+p+"/.current");
  393. versions.remove(".current");
  394. for( i in 0...versions.length ) {
  395. versions[i] = Datas.unsafe(versions[i]);
  396. if( versions[i] == current )
  397. versions[i] = "["+current+"]";
  398. }
  399. print(Datas.unsafe(p) + ": "+versions.join(" "));
  400. }
  401. }
  402. function upgrade() {
  403. var rep = getRepository();
  404. var prompt = true;
  405. var update = false;
  406. for( p in neko.FileSystem.readDirectory(rep) ) {
  407. var current = neko.io.File.getContent(rep+p+"/.current");
  408. var p = Datas.unsafe(p);
  409. print("Checking "+p);
  410. var inf = site.infos(p);
  411. if( inf.curversion != current ) {
  412. if( prompt ) {
  413. var answer;
  414. do {
  415. neko.Lib.print("Upgrade "+p+" to "+inf.curversion+" [y/n/a] ? ");
  416. answer = neko.io.File.stdin().readLine();
  417. } while( answer != "y" && answer != "n" && answer != "a" );
  418. if( answer == "n" )
  419. continue;
  420. if( answer == "a" )
  421. prompt = false;
  422. }
  423. doInstall(p,inf.curversion,true);
  424. update = true;
  425. }
  426. }
  427. if( update )
  428. print("Done");
  429. else
  430. print("All projects are up-to-date");
  431. }
  432. function deleteRec(dir) {
  433. for( p in neko.FileSystem.readDirectory(dir) ) {
  434. var path = dir+"/"+p;
  435. if( neko.FileSystem.isDirectory(path) )
  436. deleteRec(path);
  437. else
  438. neko.FileSystem.deleteFile(path);
  439. }
  440. neko.FileSystem.deleteDirectory(dir);
  441. }
  442. function remove() {
  443. var prj = param("Project");
  444. var version = paramOpt();
  445. var rep = getRepository();
  446. var pdir = rep + Datas.safe(prj);
  447. if( version == null ) {
  448. if( !neko.FileSystem.exists(pdir) )
  449. throw "Project "+prj+" is not installed";
  450. deleteRec(pdir);
  451. print("Project "+prj+" removed");
  452. return;
  453. }
  454. var vdir = pdir + "/" + Datas.safe(version);
  455. if( !neko.FileSystem.exists(vdir) )
  456. throw "Project "+prj+" does not have version "+version+" installed";
  457. var cur = neko.io.File.getContent(pdir+"/.current");
  458. if( cur == version )
  459. throw "Can't remove current version of project "+prj;
  460. deleteRec(vdir);
  461. print("Project "+prj+" version "+version+" removed");
  462. }
  463. function set() {
  464. var prj = param("Project");
  465. var version = param("Version");
  466. var pdir = getRepository() + Datas.safe(prj);
  467. var vdir = pdir + "/" + Datas.safe(version);
  468. if( !neko.FileSystem.exists(vdir) )
  469. throw "Project "+prj+" version "+version+" is not installed";
  470. var f = neko.io.File.write(pdir+"/.current",true);
  471. f.write(version);
  472. f.close();
  473. print("Project "+prj+" current version is now "+version);
  474. }
  475. function checkRec( prj : String, version : String, l : List<{ project : String, version : String }> ) {
  476. var pdir = getRepository() + Datas.safe(prj);
  477. if( !neko.FileSystem.exists(pdir) )
  478. throw "Project "+prj+" is not installed";
  479. var version = if( version != null ) version else neko.io.File.getContent(pdir+"/.current");
  480. var vdir = pdir + "/" + Datas.safe(version);
  481. if( !neko.FileSystem.exists(vdir) )
  482. throw "Project "+prj+" version "+version+" is not installed";
  483. for( p in l )
  484. if( p.project == prj ) {
  485. if( p.version == version )
  486. return;
  487. throw "Project "+prj+" has two version included "+version+" and "+p.version;
  488. }
  489. l.add({ project : prj, version : version });
  490. var xml = neko.io.File.getContent(vdir+"/haxelib.xml");
  491. var inf = Datas.readData(xml);
  492. for( d in inf.dependencies )
  493. checkRec(d.project,if( d.version == "" ) null else d.version,l);
  494. }
  495. function path() {
  496. var list = new List();
  497. while( argcur < args.length ) {
  498. var a = args[argcur++].split(":");
  499. checkRec(a[0],a[1],list);
  500. }
  501. var rep = getRepository();
  502. for( d in list ) {
  503. var pdir = Datas.safe(d.project)+"/"+Datas.safe(d.version)+"/";
  504. var dir = rep + pdir;
  505. var ndir = dir + "ndll";
  506. if( neko.FileSystem.exists(ndir) ) {
  507. var sysdir = ndir+"/"+neko.Sys.systemName();
  508. if( !neko.FileSystem.exists(sysdir) )
  509. throw "Project "+d.project+" version "+d.version+" does not have a neko dll for your system";
  510. neko.Lib.println("-L "+pdir+"ndll/");
  511. }
  512. if( dir.split(" ").length > 1 )
  513. dir = '"' + dir + '"';
  514. neko.Lib.println(dir);
  515. }
  516. }
  517. // ----------------------------------
  518. static function print(str) {
  519. neko.Lib.print(str+"\n");
  520. }
  521. static function main() {
  522. new Main().run();
  523. }
  524. }