Stream:all - add 'inval' arg, so it can be used with musical patterns
[supercollider.git] / SCClassLibrary / Common / Quarks / Quarks.sc
blobc5f9e32e86bc68bc5c5ad9f1362618a2a8d58db8
1 /**
2   *
3   * Subversion based package repository and package manager
4   * sk, cx, danstowell, LFSaw
5   *
6   *  this is the interface class for
7   *   accessing the SVN repository, (eg. the sourceforge quarks project)
8   *   downloading those to the local quarks folder (Platform.userAppSupportDir/quarks)
9   *   installing individual quarks by symlinking from the local quark folder into the [Platform dependent]/Extensions folder
10   *
11   */
14 Quarks
16         classvar global, <allInstances, <known;
17         var <repos, <local;
19         *global { ^(global ?? { global = this.new; }) }
21         *new { | reposPath, localPath |
22                 var newQ;
23                 if((this.known[reposPath].isNil.not) and: (this.known[reposPath] != localPath), {
24                         ("The repository is in the list of known repositories, but with a different local path. You are recommended to use this local path:"
25                                          + this.known[reposPath]).warn;
26                 });
27                 newQ = super.new.initQuarks(
28                         reposPath,
29                         localPath
30                 );
31                 allInstances.add(newQ);
32                 ^newQ;
33         }
34         initQuarks{|reposPath, localPath|
35                 local = LocalQuarks(localPath, this);
36                 repos = QuarkSVNRepository(reposPath, local);
37         }
39         *initClass {
40                 Class.initClassTree( Platform );
41                 allInstances = List(1);
42                 known = Dictionary[
43                         "https://quarks.svn.sourceforge.net/svnroot/quarks" -> (Platform.userAppSupportDir +/+ "quarks"),
44                         "https://svn.sonenvir.at/svnroot/SonEnvir/trunk/src/quarks-sonenvir" -> (Platform.userAppSupportDir +/+ "quarks-sonenvir"),
45                         "https://sc3-plugins.svn.sourceforge.net/svnroot/sc3-plugins/build" -> (Platform.userAppSupportDir +/+ "SC3plugins")
46                         ];
47         }
48         *forUrl { |url|
49                 var q;
50                 this.global; // ensure the global one is constructed
51                 q = allInstances.detect({|q| q.repos.url == url});
52                 if(q.isNil && this.known[url].isNil.not, {
53                         q = Quarks.new(url, this.known[url]);
54                 });
55                 ^q;
56         }
58         update { |name|
59                 var q;
60                 if(name.notNil,{
61                         if((q = local.findQuark(name)).isNil,{
62                                 Error("Local Quark "+name+" not found, cannot update from repository").throw;
63                         });
64                         repos.svn("update", (local.path+/+q.path).escapeChar($ ));
65                 },{
66                         //update all
67                         repos.update(local)
68                 });
69         }
70         /*add { |name|
71                 // this should create or manage the directory file too if it exists
72                 var q;
73                 if((q = local.findQuark(name)).isNil,{
74                         Error("Local Quark code not found, cannot add to repository").throw;
75                 });
76                 repos.svn("add",local.path++"/"++q.path);
77         }*/
78         commit { |name,message|
79                 var q;
80                 if((q = local.findQuark(name)).isNil,{
81                         Error("Local Quark code not found, cannot commit to repository").throw;
82                 });
83                 if(message.isNil,{
84                         Error("svn log message required to commit").throw;
85                 });
86                 repos.svn("commit","-m",message,"-F",local.path +/+ q.path);
87         }
88         // TODO: Deprecate "checkoutDirectory" - "updateDirectory" can be used whether or not there's an existing one
89         checkoutDirectory {
90                 "Please wait for directory to be checked out.".postln;
91                 ^this.updateDirectory
92         }
93         updateDirectory {
94                 repos.updateDirectory
95         }
97         listCheckedOut {
98                 this.checkedOut.do { |q| q.postDesc };
99         }
100         listAvailable {
101                 this.repos.quarks.do { |q| q.postDesc };
102         }
104         checkoutAll { repos.checkoutAll(local.path) }
105         checkout { |name, version, sync=false|
106                 var q;
107                 if(local.findQuark(name,version).notNil,{
108                         inform("Quark "++name++" already checked out");
109                         ^this
110                 });
112                 q = repos.findQuark(name,version);
113                 if(q.isNil,{
114                         Error("Quark not found in repository.").throw;
115                 });
116                 repos.checkout(q, local.path, sync);
117         }
118         // DEPRECATED because it has a different and confusing functionality w.r.t. QuarkSVNRepos.checkDir
119         checkDir {
120                 var d;
121                 "Quarks.checkDir is deprecated".warn;
122                 d = (Platform.userExtensionDir +/+ local.name).escapeChar($ );
123                 if(d.pathMatch.isEmpty,{
124                         ("creating: " + d).inform;
125                         ("mkdir -p" + d).systemCmd;
126                 });
127         }
128         checkedOut {
129                 ^local.quarks
130         }
131         status { |name|
132                 var q;
133                 if(name.notNil,{
134                         q = local.findQuark(name);
135                         repos.svn("status",(local.path +/+ q.path).escapeChar($ ));
136                 },{
137                         repos.svn("status",local.path.escapeChar($ ));
138                 });
139         }
140         installed {
141                 // of quarks in local, select those also present in userExtensionDir
142                 ^local.quarks.select{|q|
143                         (Platform.userExtensionDir.escapeChar($ )
144                                 +/+ local.name
145                                 +/+ q.path
146                         ).pathMatch.notEmpty
147                 }
148         }
149         install { | name , includeDependencies=true, checkoutIfNeeded=true |
150                 var q, deps, installed, dirname, quarksForDep;
152                 if(this.isInstalled(name),{
153                         (name + "already installed").inform;
154                         ^this
155                 });
157                 q = local.findQuark(name);
158                 if(q.isNil,{
159                         if(checkoutIfNeeded) {
160                                 (name.asString + " not found in local quarks; checking out from remote ...").postln;
161                                 this.checkout(name, sync: true);
162                                 q = local.reread.findQuark(name);
163                                 if(q.isNil, {
164                                         Error("Quark" + name + "install: checkout failed.").throw;
165                                 });
166                         }
167                         {
168                                 Error(name.asString + "not found in local quarks.  Not yet downloaded from the repository ?").throw;
169                         };
170                 });
172                 if(q.isCompatible.not,{
173                         (q.name + " reports that it is not compatible with your current class library.  See the help file for further information.").inform;
174                         ^this
175                 });
177                 // create /quarks/ directory if needed
178                 if(this.repos.checkDir.not){this.checkoutDirectory};
180                 // Now ensure that the dependencies are installed (if available given the current active reposses)
181                 if(includeDependencies, {
182                         q.dependencies(true).do({ |dep|
183                                 quarksForDep = if(dep.repos.isNil, {this}, {Quarks.forUrl(dep.repos)});
184                                 if(quarksForDep.isNil, {
185                                         ("Quarks:install - unable to find repository for dependency '" ++ dep.name
186                                                 ++ "' - you may need to satisfy this dependency manually. No repository detected locally with URL "++dep.repos).warn;
187                                 }, {
188                                         if(quarksForDep.isInstalled(dep.name).not, {
189                                                 try({
190                                                         quarksForDep.install(dep.name, false, checkoutIfNeeded)
191                                                 }, {
192                                                         ("Unable to satisfy dependency of '"++name++"' on '"++dep.name
193                                                                 ++"' - you may need to install '"++dep.name++"' manually.").warn;
194                                                 });
195                                         });
196                                 });
197                         });
198                 });
200                 // Ensure the correct folder-hierarchy exists first
201                 dirname = (Platform.userExtensionDir +/+  local.name +/+ q.path).dirname;
202                 if(File.exists(dirname).not, {
203                         ("mkdir -p " + dirname.escapeChar($ )).systemCmd;
204                 });
206                 // install via symlink to Extensions/<quarks-dir>
207                 ("ln -s " +  (local.path +/+ q.path).escapeChar($ ) +  (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
208                 (q.name + "installed").inform;
209         }
210         listInstalled {
211                 this.installed.do { |q| q.postDesc };
212         }
213         isInstalled { arg name;
214                 ^this.installed.detect{|quark| quark.name == name }.notNil
215         }
216         uninstall { | name |
217                 var q, deps, installed;
218                 name = name.asString;
219                 if(this.isInstalled(name).not,{
220                         ^this
221                 });
223                 q = local.findQuark(name);
224                 if(q.isNil,{
225                         Error(
226                                 name +
227                                 "is not found in Local quarks in order to look up its relative path.  You may remove the symlink manually."
228                         ).throw;
229                 });
231                 // install via symlink to Extensions/Quarks
232                 ("rm " +  (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
233                 (q.name + "uninstalled").inform;
234         }
236         help { |name|
237                 var q, helpdoc, path;
239                 q = local.findQuark(name);
240                 if(q.isNil,{
241                         Error(
242                                 name.asString +
243                                 "not found in local quarks.  Not yet downloaded from the repository ?"
244                         ).throw;
245                 });
247                 helpdoc = q.info.helpdoc;
249                 if(helpdoc.isNil, {
250                         ("No primary helpdoc listed for Quark"+name).inform;
251                 }, {
252                         path = Quarks.local.path.select{|c| (c != $\\)}
253                                 +/+ q.path +/+ helpdoc;
254                         Document.open(path);
255                 });
256         }
258         name { ^local.name }
260         ///// convenience methods for Quarks.global
262         *checkoutAll { this.global.repos.checkoutAll(this.global.local.path) }
263         *checkout { | name, version, sync | this.global.checkout(name,version, sync); }
265         /*
266          return Quark objects for each in App Support/SuperCollider/Quarks
267          (so actually this would list any Quarks that are in local development
268          and not yet checked in) */
269         *checkedOut { ^this.global.checkedOut }
270         *listCheckedOut {
271                 this.checkedOut.do { |q| q.postDesc };
272         }
273         *listAvailable {
274                 this.global.listAvailable
275         }
276         /*
277           download/update only the quark specification files from remote repos
278           and not the quarks themselves */
279         *updateDirectory {
280                 ^this.global.repos.updateDirectory
281         }
282         *checkoutDirectory {
283                 ^this.global.checkoutDirectory
284         }
285         *update { |quarkName| this.global.update(quarkName) }
287         /*
288           this symlinks from {App Support}/SuperCollider/Quarks to
289           {App Support}/SuperCollider/Extensions
290           it is then in the SC compile path */
291         *install { |name, includeDependencies=true, checkoutIfNeeded=true|
292                 this.global.install(name, includeDependencies, checkoutIfNeeded)
293         }
295         /*
296           return Quark objects for each installed */
297         *installed { ^this.global.installed }
298         *listInstalled { ^this.global.listInstalled }
299         *isInstalled { |name| ^this.global.isInstalled(name) }
300         /*
301           removes the symlink */
302         *uninstall { |name| ^this.global.uninstall(name) }
304         /*
305           add code in {App Support}/SuperCollider/Quarks to the remote repos
306           and also adds the quarks file in DIRECTORY */
307         //*add { |name| this.global.add(name) }
308         /*
309           you may also use standard svn tools within {App Support}/SuperCollider/Quarks */
310         *commit { |name,message| this.global.commit(name,message) }
311         // post the SVN status
312         *status { |name| this.global.status(name) }
314         *local { ^this.global.local }
315         *repos { ^this.global.repos }
316         *help  {|name| ^this.global.help(name) }
317         *gui {
318                 ^this.global.gui
319         }
321         // a gui for Quarks. 2007 by LFSaw.de
322         gui {
323                 var     window, caption, explanation, views, resetButton, saveButton, warning,
324                         scrollview, scrB, flowLayout, /* quarksflow, */ height, maxPerPage, nextButton, prevButton;
325                 var     quarks;
326                 var pageStart = 0, fillPage = { |start|
327                         scrollview.visible = false;
328                         views.notNil.if({
329                                 views.do({ |view| view.remove });
330                         });
331                         scrollview.decorator.reset;
332                         views = quarks.collect{|quark|
333                                 var qView = QuarkView.new(scrollview, 500@20, quark,
334                                         this.installed.detect{|it| it == quark}.notNil);
335                                 scrollview.decorator.nextLine;
336                                 qView;
337                         };
338                         scrollview.visible = true;
339                         views
340                 };
342                 // note, this doesn't actually contact svn
343                 // it only reads the DIRECTORY entries you've already checked out
344                 quarks = this.repos.quarks.copy
345                         .sort({ |a, b| a.name < b.name });
347                 scrB = GUI.window.screenBounds;
348                 height = min(quarks.size * 25 + 120, scrB.height - 60);
350                 window = GUI.window.new(this.name, Rect.aboutPoint( scrB.center, 250, height.div( 2 )));
351                 flowLayout = FlowLayout( window.view.bounds );
352                 window.view.decorator = flowLayout;
354                 caption = GUI.staticText.new(window, Rect(20,15,400,30));
355                 caption.font_( GUI.font.new( GUI.font.defaultSansFace, 24 ));
356                 caption.string = this.name;
357                 window.view.decorator.nextLine;
359                 if ( quarks.size == 0 ){
360                         GUI.button.new(window, Rect(0, 0, 229, 20))
361                         .states_([["checkout Quarks DIRECTORY", Color.black, Color.gray(0.5)]])
362                         .action_({ this.checkoutDirectory; });
363                 }{
364                         GUI.button.new(window, Rect(0, 0, 229, 20))
365                         .states_([["update Quarks DIRECTORY", Color.black, Color.gray(0.5)]])
366                         .action_({ this.updateDirectory;});
367                 };
369                 GUI.button.new(window, Rect(0, 0, 200, 20))
370                 .states_([["refresh Quarks listing", Color.black, Color.gray(0.5)]])
371                 .action_({
372                         window.close;
373                         this.gui;
374                 });
376                 window.view.decorator.nextLine;
378                 GUI.button.new(window, Rect(0, 0, 150, 20))
379                         .states_([["browse all help", Color.black, Color.gray(0.5)]])
380                         .action_({ Help(this.local.path).gui });
382                 // add open directory button (open is only implemented in OS X)
383                 (thisProcess.platform.name == \osx).if{
384                         GUI.button.new(window, Rect(15,15,150,20)).states_([["open quark directory", Color.black, Color.gray(0.5)]]).action_{ arg butt;
385                                 "open %".format(this.local.path.escapeChar($ )).unixCmd;
386                         };
387                 };
389                 resetButton = GUI.button.new(window, Rect(15,15,75,20));
390                 resetButton.states = [
391                         ["reset", Color.black, Color.gray(0.5)]
392                 ];
393                 resetButton.action = { arg butt;
394                         views.do(_.reset);
395                 };
397                 saveButton = GUI.button.new(window, Rect(15,15,75,20));
398                 saveButton.states = [
399                         ["save", Color.black, Color.blue(1, 0.5)]
400                 ];
401                 saveButton.action = { arg butt;
402                         Task{
403                                 warning.string = "Applying changes, please wait";
404                                 warning.background_(Color(1.0, 1.0, 0.9));
405                                 0.1.wait;
406                                 views.do{|qView|
407                                         qView.toBeInstalled.if({
408                                                 this.install(qView.quark.name);
409                                                 qView.flush
410                                         });
411                                         qView.toBeDeinstalled.if({
412                                                 this.uninstall(qView.quark.name);
413                                                 qView.flush;
414                                         })
415                                 };
416                                 warning.string = "Done. You should now recompile sclang";
417                                 warning.background_(Color(0.9, 1.0, 0.9));
418                         }.play(AppClock);
419                 };
421                 window.view.decorator.nextLine;
422                 explanation = GUI.staticText.new(window, Rect(20,15,500,20));
423                 explanation.string = "\"+\" -> installed, \"-\" -> not installed, \"*\" -> marked to install, \"x\" -> marked to uninstall";
424                 window.view.decorator.nextLine;
426                 warning = GUI.staticText.new(window, Rect(20,15,400,30));
427                 warning.font_( GUI.font.new( GUI.font.defaultSansFace, 18 ));
429                 window.view.decorator.nextLine;
430                 GUI.staticText.new( window, 492 @ 1 ).background_( Color.grey );                window.view.decorator.nextLine;
432                 flowLayout.margin_( 0 @0 ).gap_( 0@0 );
433                 scrollview = GUI.scrollView.new(window, 500 @ (height - 165))
434                         .resize_( 5 )
435                         .autohidesScrollers_(true);
436                 scrollview.decorator = FlowLayout( Rect( 0, 0, 500, quarks.size * 25 + 20 ));
438                 window.front;
439                 fillPage.(pageStart);
440                 ^window;
441         }