added simple package manager UI
[guerillascript.git] / main / modules / concmd.js
blob2744210de867f5269db046d9dfe82f0a3fe599f5
1 /* coded by Ketmar // Invisible Vector (psyc://ketmar.no-ip.org/~Ketmar)
2  * Understanding is not required. Only obedience.
3  *
4  * This program is free software. It comes without any warranty, to
5  * the extent permitted by applicable law. You can redistribute it
6  * and/or modify it under the terms of the Do What The Fuck You Want
7  * To Public License, Version 2, as published by Sam Hocevar. See
8  * http://www.wtfpl.net/txt/copying/ for more details.
9  */
10 ////////////////////////////////////////////////////////////////////////////////
11 uses("resource://gre/modules/devtools/gcli.jsm");
13 require("utils/utils");
14 let {oneShotTimer, intervalTimer} = require("utils/timer");
17 ////////////////////////////////////////////////////////////////////////////////
18 function wild2re (wildstr) {
19   wildstr = wildstr.replace(/^\s+/, "").replace(/\s+$/, "");
20   if (wildstr == "") wildstr = "*";
21   if (wildstr == "*") return new RegExp(/./);
22   // regexp?
23   if (wildstr.length >= 2 && wildstr.charCodeAt(0) == 47 && wildstr.charCodeAt(wildstr.length-1) == 47) {
24     if (wildstr.length < 3) return new RegExp(/./);
25     return new RegExp(wildstr.substring(1, wildstr.length-1), "i");
26   }
27   // convert glob to regexp
28   let re = "^";
29   for (let f = 0; f < wildstr.length; ++f) {
30     switch (wildstr[f]) {
31       case "*": re += ".*"; break; // any, greedy
32       case ".": case "?": case "^": case "$": case "+":
33       case "{": case "}": case "[": case "]": case "|":
34       case "(": case ")": case "\\":
35         re += "\\"+wildstr[f];
36         break;
37       case " ": re += "\\s"; break;
38       default: re += wildstr[f]; break;
39     }
40   }
41   re += "$";
42   //if (addonOptions.debugCache) conlog("wildstr:["+wildstr+"]  re: ["+re+"]");
43   return new RegExp(re, "i");
47 ////////////////////////////////////////////////////////////////////////////////
48 let editList = {lmod:-1};
49 let editNames;
52 function getEditList () {
53   let cur = scacheAPI.getScriptsForEdit();
54   if (cur.lmod != editList.lmod) {
55     editList = {lmod:cur.lmod, list:cur.list};
56     editNames = new Array();
57     for (let xnfo of editList.list) editNames.push({name:xnfo.name, value:xnfo.path});
58   }
59   return editNames;
63 ////////////////////////////////////////////////////////////////////////////////
64 let pkgList = {lmod:-1};
65 let pkgNames;
68 function getPkgList () {
69   let cur = pkgman.getPackageList();
70   //conlog("getPkgList: pkgList.lmod=", pkgList.lmod, "; cur.lmod=", cur.lmod);
71   if (cur.lmod != pkgList.lmod) {
72     pkgList = {lmod:cur.lmod, list:cur.list};
73     pkgNames = new Array();
74     for (let pi of pkgList.list) pkgNames.push({name:pi.name, value:pi});
75   }
76   return pkgNames;
80 ////////////////////////////////////////////////////////////////////////////////
81 function addjsext (fn) {
82   if (!/\.js$/.test(fn)) fn += ".js";
83   return fn;
87 ////////////////////////////////////////////////////////////////////////////////
88 function openEditor (fname, chromeWin, newfile) {
89   let fl = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
90   fl.followLinks = true;
91   fl.initWithPath(fname);
92   let lmtime = 0;
93   if (fl.exists()) {
94     if (fl.isDirectory()) return;
95     lmtime = fl.lastModifiedTime;
96   } else {
97     if (!newfile) return;
98   }
99   let text;
100   try {
101     text = fileReadText(fl);
102     newfile = false;
103   } catch (e) {
104     if (!newfile) {
105       logError("can't read file: "+fname);
106       return;
107     }
108     text =
109       "// ==UserScript==\n"+
110       "// @name        \n"+
111       "// @description \n"+
112       "// @version     1.0\n"+
113       "// @include     *\n"+
114       "// @run-at      document-end\n"+
115       "// @noframes\n"+
116       "// @nowrap\n"+
117       "// @libraries   \n"+
118       "// @require     \n"+
119       "// ==/UserScript==\n\n";
120   }
121   let spw = chromeWin.Scratchpad.ScratchpadManager.openScratchpad({
122     filename: fname,
123     text: text,
124     saved: !newfile,
125   });
126   // TODO: how can i observe file changes without timer?
127   let wasChange = false;
128   let checkFC = function () {
129     let fl = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
130     fl.followLinks = true;
131     fl.initWithPath(fname);
132     if (fl.exists() && !fl.isDirectory() && lmtime != fl.lastModifiedTime) {
133       lmtime = fl.lastModifiedTime;
134       return true;
135     }
136     return false;
137   };
138   let tm = intervalTimer(function () {
139     if (checkFC()) { scacheAPI.reset(); wasChange = true; }
140   }, 1500);
141   // kill timer on closing
142   spw.addEventListener("close", function sploaded() {
143     spw.removeEventListener("close", sploaded, false);
144     tm.cancel();
145     // just in case
146     if (wasChange || checkFC()) {
147       scacheAPI.reset();
148       scacheAPI.validate();
149       latestUC = scacheAPI.getUC();
150     }
151   }, false);
155 ////////////////////////////////////////////////////////////////////////////////
156 let gcliCommandSpecs = [
157   {
158     name: "guerilla",
159     description: "GuerillaJS control",
160   },
161   // subcommands
162   {
163     name: "guerilla about",
164     description: "show various info",
165     params: [
166       {
167         name: "type",
168         description: "info to show",
169         type: { name: "selection", data: ["readme", "credits", "thanks", "licenses"] },
170         defaultValue: "readme",
171       }
172     ],
173     exec: function (args, context) {
174       let bro = context.environment.chromeWindow.gBrowser;
175       let list;
176       switch (args.type) {
177         case "credits": list = "CREDITS.txt"; break;
178         case "thanks": list = "THANKS.txt"; break;
179         case "licenses": list = ["LICENSE.bsd.txt", "LICENSE.mit.txt", "LICENSE.mpl.txt", "LICENSE.wtfpl.txt"]; break;
180         default: list = "README.txt"; break;
181       }
182       if (typeof(list) == "string") {
183         bro.selectedTab = bro.addTab(gsdoxUrl+list);
184       } else {
185         for (let name of list) bro.selectedTab = bro.addTab(gsdoxUrl+name);
186       }
187     },
188   },
189   //
190   {
191     name: "guerilla reset",
192     description: "reset all internal caches",
193     exec: function (args, context) {
194       //conlog("clearing guerilla caches...");
195       scacheAPI.reset();
196     },
197   },
198   //
199   {
200     name: "guerilla debug",
201     description: "switch debug mode on/off",
202     params: [
203       {
204         name: "flag",
205         description: "action to perform",
206         type: { name: "selection", data: ["show", "tan", "ona", "toggle"] },
207         defaultValue: "show",
208       }
209     ],
210     returnValue: "string",
211     exec: function (args, context) {
212       switch (args.flag) {
213         case "tan": addonOptions.debugMode = true; break;
214         case "ona": addonOptions.debugMode = false; break;
215         case "toggle": addonOptions.debugMode = !addonOptions.debugMode; break;
216       }
217       return "guerilla debug mode is "+(addonOptions.debugMode ? "on" : "off");
218     },
219   },
220   //
221   {
222     name: "guerilla log",
223     description: "switch logging on/off",
224     params: [
225       {
226         name: "flag",
227         description: "action to perform",
228         type: { name: "selection", data: ["show", "tan", "ona", "toggle"] },
229         defaultValue: "show",
230       }
231     ],
232     returnValue: "string",
233     exec: function (args, context) {
234       switch (args.flag) {
235         case "tan": addonOptions.logEnabled = true; break;
236         case "ona": addonOptions.logEnabled = false; break;
237         case "toggle": addonOptions.logEnabled = !addonOptions.logEnabled; break;
238       }
239       return "guerilla logging is "+(addonOptions.logEnabled ? "on" : "off");
240     },
241   },
242   //
243   {
244     name: "guerilla state",
245     description: "switch guerilla state",
246     params: [
247       {
248         name: "flag",
249         description: "action to perform",
250         type: { name: "selection", data: ["show", "active", "inactive", "toggle"] },
251         defaultValue: "show",
252       }
253     ],
254     returnValue: "string",
255     exec: function (args, context) {
256       switch (args.flag) {
257         case "active": addonOptions.active = true; break;
258         case "inactive": addonOptions.active = false; break;
259         case "toggle": addonOptions.active = !addonOptions.active; break;
260       }
261       return "guerilla is "+(addonOptions.active ? "" : "in")+"active";
262     },
263   },
264   //
265   {
266     name: "guerilla activate",
267     description: "activate guerilla",
268     returnValue: "string",
269     exec: function (args, context) {
270       addonOptions.active = true;
271       return "guerilla is active";
272     },
273   },
274   //
275   {
276     name: "guerilla deactivate",
277     description: "deactivate guerilla",
278     returnValue: "string",
279     exec: function (args, context) {
280       addonOptions.active = false;
281       return "guerilla is inactive";
282     },
283   },
284   //
285   {
286     name: "guerilla new",
287     description: "create new userscript",
288     params: [
289       {
290         name: "filename",
291         description: "new script name",
292         type: "string",
293       }
294     ],
295     exec: function (args, context) {
296       let fname = args.filename;
297       let mt = fname.match(/^\/?libs\/([^\/]+)$/);
298       if (mt) fname = mt[1];
299       if (fname.length == 0 || fname.indexOf("/") >= 0) return;
300       fname = addjsext(fname);
301       let dir;
302       if (mt) {
303         // new library
304         dir = getUserLibDir();
305         dir.append(fname);
306       } else {
307         //if (args.filename.length == 0 || args.filename.indexOf("/") >= 0) { alert("invalid file name: "+args.filename); return; }
308         dir = getUserJSDir();
309         dir.append(fname);
310       }
311       conlog("trying to edit '"+dir.path+"'");
312       openEditor(dir.path, context.environment.chromeDocument.defaultView, true);
313     },
314   },
315   //
316   {
317     name: "guerilla edit",
318     description: "edit guerilla script",
319     //returnValue: "string",
320     params: [
321       {
322         name: "filename",
323         description: "script name to edit",
324         type: {
325           name: "selection",
326           cacheable: false,
327           lookup: function (context) getEditList(),
328         }, // type
329       }
330     ],
331     exec: function (args, context) {
332       if (args.filename) openEditor(args.filename, context.environment.chromeDocument.defaultView);
333     },
334   },
335   // package backend
336   {
337     name: "guerilla package",
338     description: "GuerillaJS package control",
339   },
340   {
341     name: "guerilla package abort",
342     description: "abort all operations",
343     exec: function (args, context) { pkgman.cancel(); },
344   },
345   {
346     name: "guerilla package list",
347     description: "list installed packages",
348     returnValue: "string",
349     params: [
350       {
351         name: "mask",
352         description: "mask",
353         defaultValue: "*",
354         type: "string",
355       }
356     ],
357     exec: function (args, context) {
358       let mask = args.mask||"*";
359       let re = wild2re(mask);
360       let res = "=== package list ===";
361       let count = 0;
362       for (let pi of pkgDB.getActivePackages()) {
363         if (re.test(pi.name)) {
364           res += "\n"+pi.name;
365           if (pi.version) res += "\t"+pi.version;
366           ++count;
367         }
368       }
369       //return (count ? res : "no packages found");
370       //TODO: special output
371       logError(count ? res : "no packages found");
372       return "find result in error console";
373     },
374   },
375   {
376     name: "guerilla package update",
377     description: "update package(s)",
378     params: [
379       {
380         name: "mask",
381         description: "mask",
382         defaultValue: "",
383         type: "string",
384       }
385     ],
386     exec: function (args, context) {
387       if (!args.mask) return;
388       let mask = args.mask||"*";
389       let re = wild2re(mask);
390       for (let pi of pkgDB.getActivePackages()) {
391         if (re.test(pi.name)) {
392           try {
393             pkgman.update(pi.name);
394           } catch (e) {
395             logError("ERROR: ", e.message);
396           }
397         }
398       }
399     },
400   },
401   {
402     name: "guerilla package install",
403     description: "install package",
404     params: [
405       {
406         name: "name",
407         description: "package name",
408         type: "string",
409       },
410       {
411         name: "url",
412         description: "package install url",
413         type: "string",
414       }
415     ],
416     exec: function (args, context) {
417       if (!args.name || !args.url) return;
418       try {
419         pkgman.install(args.name, args.url);
420       } catch (e) {
421         logError("ERROR: ", e.message);
422       }
423     },
424   },
425   {
426     name: "guerilla package remove",
427     description: "remove package",
428     params: [
429       {
430         name: "name",
431         description: "package name",
432         type: {
433           name: "selection",
434           cacheable: false,
435           lookup: function (context) getPkgList(),
436         }, // type
437       }
438     ],
439     exec: function (args, context) {
440       //if (!args.name.name) return;
441       try {
442         pkgman.remove(args.name.name);
443       } catch (e) {
444         logError("ERROR: ", e.message);
445       }
446     },
447   }
451 ////////////////////////////////////////////////////////////////////////////////
452 registerStartupHook("gcli", function () {
453   for (let cmd of gcliCommandSpecs) {
454     if (cmd && typeof(cmd) === "object" && cmd.name) gcli.addCommand(cmd);
455   }
459 registerShutdownHook("gcli", function () {
460   for (let cmd of gcliCommandSpecs) {
461     if (cmd && typeof(cmd) === "object" && cmd.name) gcli.removeCommand(cmd);
462   }