Trying out friendly AI. Some code reorganization.
authordsc <david.schoonover@gmail.com>
Fri, 26 Nov 2010 21:16:36 +0000 (13:16 -0800)
committerdsc <david.schoonover@gmail.com>
Fri, 26 Nov 2010 21:16:36 +0000 (13:16 -0800)
39 files changed:
bin/check-css.sh [new file with mode: 0755]
css/reset-old.css [new file with mode: 0644]
css/reset.css
css/reset.min.css [new file with mode: 0644]
src/Y/y-object.js
src/easel/layer.js [moved from src/portal/layer.js with 100% similarity]
src/easel/loop/cooldown.js [moved from src/portal/loop/cooldown.js with 100% similarity]
src/easel/loop/eventloop.js [moved from src/portal/loop/eventloop.js with 100% similarity]
src/easel/loop/fps.js [moved from src/portal/loop/fps.js with 100% similarity]
src/easel/math/line.js [moved from src/portal/math/line.js with 100% similarity]
src/easel/math/math.js [moved from src/portal/math/math.js with 100% similarity]
src/easel/math/vec.js [moved from src/portal/math/vec.js with 100% similarity]
src/easel/shape/circle.js [moved from src/portal/shape/circle.js with 100% similarity]
src/easel/shape/line.js [moved from src/portal/shape/line.js with 100% similarity]
src/easel/shape/polygon.js [moved from src/portal/shape/polygon.js with 100% similarity]
src/easel/shape/rect.js [moved from src/portal/shape/rect.js with 100% similarity]
src/easel/shape/shape.js [moved from src/portal/shape/shape.js with 100% similarity]
src/easel/util/astar.js [moved from src/portal/util/astar.js with 100% similarity]
src/easel/util/binaryheap.js [moved from src/portal/util/binaryheap.js with 100% similarity]
src/easel/util/graph.js [moved from src/portal/util/graph.js with 100% similarity]
src/easel/util/tree/pointquadtree.js [moved from src/portal/util/tree/pointquadtree.js with 100% similarity]
src/easel/util/tree/quadtree.js [moved from src/portal/util/tree/quadtree.js with 100% similarity]
src/easel/util/tree/rbtree.js [moved from src/portal/util/tree/rbtree.js with 100% similarity]
src/tanks/game.js
src/tanks/map/level.js
src/tanks/map/pathmap.js
src/tanks/thing/bullet.js
src/tanks/ui/main.js
src/tanks/ui/ui-config.js [moved from src/tanks/ui/config.js with 72% similarity]
src/tanks/util/config.js
tanks.php
test/jsc/bar.js [new file with mode: 0644]
test/jsc/baz.js [new file with mode: 0644]
test/jsc/foo.js [new file with mode: 0644]
test/jsc/jsc.py [new file with mode: 0755]
test/jsc/lol.js [new file with mode: 0644]
test/math/index.php
test/uki/index.php [new file with mode: 0644]
test/uki/test-uki.js [new file with mode: 0644]

diff --git a/bin/check-css.sh b/bin/check-css.sh
new file mode 100755 (executable)
index 0000000..4bc5456
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/local/bin/fish
+echo "$_" (dirname "$_")
+# ls -lia ~/dev/lang/css/reset* (dirname $_)/css/reset* | sort
diff --git a/css/reset-old.css b/css/reset-old.css
new file mode 100644 (file)
index 0000000..dd5886b
--- /dev/null
@@ -0,0 +1,2 @@
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
+h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;} em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;}
\ No newline at end of file
index dd5886b..0fbb22a 100644 (file)
@@ -1,2 +1,47 @@
-html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
-h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;} em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;}
\ No newline at end of file
+html { color:#000; background:#fff; width:100%; height:100%; }
+body { position:absolute; width:100%; top:0; left:0; }
+html,body,div,
+       dl,dt,dd,ul,ol,li,
+       h1,h2,h3,h4,h5,h6,
+       pre,code,
+       form,fieldset,legend,input,button,textarea,
+       p,blockquote,
+       th,td { margin:0; padding:0; }
+
+address,caption,cite,code,dfn,em,strong,th,var,optgroup { font-style:inherit; font-weight:inherit; }
+fieldset,img { border:0; }
+caption,th { text-align:left; }
+caption { margin-bottom:.5em; text-align:center; }
+
+h1,h2,h3,h4,h5,h6 { font-size:100%; font-weight:normal; }
+h1,h2,h3,h4,h5,h6,strong { font-weight:bold; }
+h1 { font-size:138.5%; }
+h2 { font-size:123.1%; }
+h3 { font-size:108%; }
+h1,h2,h3 { margin:0.5em 0 1em; }
+
+q:before,q:after { content:''; }
+del,ins { text-decoration:none; }
+abbr,acronym { border:0; font-variant:normal; border-bottom:1px dotted #000; cursor:help; }
+sup, sub { vertical-align:baseline; }
+em { font-style:italic; }
+
+input,button,textarea,select,optgroup,option { font-family:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; }
+input,button,textarea,select { *font-size:100%; }
+input[type=text],input[type=password],textarea { width:12.25em; *width:11.9em; }
+legend { color:#000; }
+
+ul,ol,dl,blockquote { margin:1em; }
+ol,ul,dl { margin-left:2em; }
+li { list-style:none; }
+ol li { list-style:decimal outside; }
+ul li { list-style:disc outside; }
+dl dd { margin-left:1em; }
+
+table { border-collapse:collapse; border-spacing:0; }
+th,td { padding:.5em; }
+th { font-weight:bold; text-align:center; }
+p,fieldset,table,pre { margin-bottom:1em; }
+
+.clearer { clear:both !important; float:none !important; margin:0 !important; padding:0 !important; }
+.rounded { border-radius:1em; -moz-border-radius:1em; -webkit-border-radius:1em; }
\ No newline at end of file
diff --git a/css/reset.min.css b/css/reset.min.css
new file mode 100644 (file)
index 0000000..3c93c02
--- /dev/null
@@ -0,0 +1 @@
+html{color:#000;background:#fff;width:100%;height:100%;}body{position:absolute;width:100%;top:0;left:0;}html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}fieldset,img{border:0;}caption,th{text-align:left;}caption{margin-bottom:.5em;text-align:center;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:.5em 0 1em;}q:before,q:after{content:'';}del,ins{text-decoration:none;}abbr,acronym{border:0;font-variant:normal;border-bottom:1px dotted #000;cursor:help;}sup,sub{vertical-align:baseline;}em{font-style:italic;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;}legend{color:#000;}ul,ol,dl,blockquote{margin:1em;}ol,ul,dl{margin-left:2em;}li{list-style:none;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}table{border-collapse:collapse;border-spacing:0;}th,td{padding:.5em;}th{font-weight:bold;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}.clearer{clear:both!important;float:none!important;margin:0!important;padding:0!important;}.rounded{border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em;}
\ No newline at end of file
index b286350..2d6b497 100644 (file)
@@ -27,12 +27,13 @@ YCollection.subclass('YObject', {
     /**
      * Searches a heirarchical object for a given subkey specified in dotted-property syntax.
      * @param {Array|String} chain The property-chain to lookup.
+     * @param {Any} [def] Default value should the key be `undefined`.
      * @param {Boolean} [meta] If supplied return an object of the form
       *         `{ key: Qualified key name, obj: Parent object of key, value: Value at obj[key] }` 
       *     if chain is found (and `undefined` otherwise).
-     * @return {Any} The value at the path, or `undefined`.
+     * @return {Any} The value at the path, or `def` if `undefined`, otherwise `undefined`.
      */
-    getNested : function getNested(chain, meta){
+    getNested : function getNested(chain, def, meta){
         if ( !isArray(chain) )
             chain = chain.toString().split('.');
         
@@ -48,7 +49,10 @@ YCollection.subclass('YObject', {
                 };
         }, { value:this._o });
         
-        return (ret && !meta ? ret.value : ret);
+        if (ret !== undefined)
+            return (meta ? ret : ret.value);
+        else
+            return (meta ? { value:def } : def);
     },
     
     /**
similarity index 100%
rename from src/portal/layer.js
rename to src/easel/layer.js
similarity index 100%
rename from src/portal/loop/fps.js
rename to src/easel/loop/fps.js
similarity index 100%
rename from src/portal/math/vec.js
rename to src/easel/math/vec.js
index e6cdbf0..8839f36 100644 (file)
@@ -43,7 +43,7 @@ tanks.Game = Y.subclass('Game', {
         this.root.draw();
         
         this.pathmap.removeOverlay(this.viewport);
-        if (tanks.config.pathing.overlayPathmap)
+        if (tanks.config.values.pathing.overlayPathmap)
             this.pathmap.overlay(this.viewport);
     },
     
@@ -59,7 +59,7 @@ tanks.Game = Y.subclass('Game', {
         SECONDTH = ELAPSED / 1000;
         SQUARETH = REF_SIZE * SECONDTH
         
-        if (!tanks.config.pathing.overlayAIPaths)
+        if (!tanks.config.values.pathing.overlayAIPaths)
             this.pathmap.hidePaths();
         
         this.active.invoke('updateCooldowns', NOW);
@@ -67,11 +67,11 @@ tanks.Game = Y.subclass('Game', {
         
         this.draw();
         
-        if (this.player.dead) {
+        if ( this.player.dead ) {
             this.fire('lose', this.player);
             this.stop();
             
-        } else if ( !this.units.filter('!_.dead'.lambda()).remove(this.player).size() ) {
+        } else if ( !this.units.filter('_.align !== 1 && !_.dead'.lambda()).size() ) {
             this.fire('win', this.player);
             this.stop();
         }
index 9c050d2..2dc4857 100644 (file)
@@ -26,6 +26,7 @@ Level = Rect.subclass('Level', {
         
         P = 
         game.player = game.addUnit(new PlayerTank(1), 5,9);
+        game.addUnit(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9);
         
         E = 
         game.addUnit(new Tank(2), 0,1);
index 5cdf075..7284034 100644 (file)
@@ -207,7 +207,7 @@ PathMap = QuadTree.subclass('PathMap', {
         ,   path = Y(astar.search(grid, startN, endN))
         ;
         
-        if (tanks.config.pathing.overlayAIPaths)
+        if (tanks.config.values.pathing.overlayAIPaths)
             this.drawPath(id, startN, path);
         
         return path
index 331aa9d..2dc640d 100644 (file)
@@ -91,7 +91,7 @@ Bullet = Thing.subclass('Bullet', {
         if (this.tline) { this.tline.remove(); delete this.tline; }
         if (this.shape) this.shape.remove();
         
-        if (tanks.config.pathing.traceTrajectories) {
+        if (tanks.config.values.pathing.traceTrajectories) {
             var t = this.trajectory;
             this.tline = Line.fromPoints(t.x1,t.y1, t.x2,t.y2)
                 .attr('drawDefinitionPoints', true)
index e6c3598..7f697b2 100644 (file)
@@ -124,7 +124,7 @@ function startGame(evt){
     
     $('#welcome').hide();
     
-    if ( tanks.config.ui.showCountdown )
+    if ( tanks.config.values.ui.showCountdown )
         countdownToStart(3, readyToStart);
     else
         readyToStart();
similarity index 72%
rename from src/tanks/ui/config.js
rename to src/tanks/ui/ui-config.js
index 9076096..c32d26b 100644 (file)
@@ -1,21 +1,5 @@
 (function(){
 
-var upperPat = /^[A-Z]+$/
-,   symbolPat = /^[^a-zA-Z]+$/
-;
-
-Y.YString.prototype.splitCamel =
-    function splitCamel(s){
-        return this.reduce(
-                function(acc, ch, i){
-                    return acc + (
-                        symbolPat.test(ch) ? '' : 
-                        (upperPat.test(ch) ? ' '+ch : ch) );
-                }, '')
-            .split(' ');
-    };
-
-
 // TODO
 // ====
 // - store at tanks.config.values, but clone at start to t.c.defaults
@@ -33,11 +17,15 @@ Y.YString.prototype.splitCamel =
 // - If different from default, set cookie
 // - Update tanks.config.values
 
+
 var ns = tanks.ui.config = {}
 ,   c = tanks.config.values
 ,   p = c.pathing
 ;
 
+
+
+
 ns.init = function initConfigUi(){
     $('#config [name=pathmap]').attr('checked', p.overlayPathmap);
     $('#config [name=aipaths]').attr('checked', p.overlayAIPaths);
@@ -56,5 +44,47 @@ ns.update = function updateConfigUi(evt){
     $('#viewport .layer.grid')[(c.ui.showGridCoords ? 'add' : 'remove')+'Class']('showGridCoords');
 };
 
+// var templates = {
+//     'label'    : { html:'<label for="{id}">{label}</label>' },
+//     'input'    : { html:'<input id="{id}" type="{type}" name="{key}" value="{value}">' },
+//     'text'     : { include:'input', type:'text' },
+//     'checkbox' : { include:'input', type:'checkbox', props:{ 'checked':false } },
+//     'radio'    : { include:'input', type:'radio' }
+//     // 'select'   : '',
+//     // 'textarea' : '',
+//     // 'hidden'   : '',
+//     // 'button'   : ''
+// };
+// 
+// var type2el = {
+//     'Boolean'
+//     'Number' : 
+//     'String' : 'text'
+//     // 'Array'
+//     // 'Date'
+//     // 'RegExp'
+//     // 'Function'
+//     // 'Object'
+// };
+// ns.makeField = function makeField(key, value){
+//     
+// };
+
+var upperPat = /^[A-Z]+$/
+,   symbolPat = /^[^a-zA-Z]+$/
+;
+
+Y.YString.prototype.splitCamel =
+    function splitCamel(s){
+        return this.reduce(
+                function(acc, ch, i){
+                    return acc + (
+                        symbolPat.test(ch) ? '' : 
+                        (upperPat.test(ch) ? ' '+ch : ch) );
+                }, '')
+            .split(' ');
+    };
+
+
 
 })();
index 2404089..822e579 100644 (file)
@@ -1,5 +1,93 @@
-Config = Y.subclass('Config', {
-    
+Config = Y.YObject.subclass('Config', function(){
     
+    Y.op.extend(this, {
+        
+        init : function initConfig(defaults){
+            this._defaults = defaults;
+            this._o = Y({}, defaults);
+            this.__emitter__ = new Y.event.Emitter(this);
+        },
+        
+        set : function set(key, value, def){
+            if ( key !== undefined ){
+                var meta = this.ensure(key).getNested(key, def, true)
+                ,   old = meta.value ;
+                def   = (def   === undefined ? old : def);
+                value = (value === undefined ? def : value);
+                
+                meta.obj[meta.key] = value;
+                this._fireMutation('set', key, old, value, meta.obj);
+            }
+            return this;
+        },
+        
+        remove : function remove(key){
+            if ( key !== undefined ){
+                var sentinel = {}
+                ,   meta = this.getNested(key, sentinel, true)
+                ,   old = (meta.value === sentinel ? undefined : old);
+                
+                if ( meta.obj ) {
+                    delete meta.obj[meta.key];
+                    this._fireMutation('remove', key, old, undefined, meta.obj);
+                }
+            }
+            return this;
+        },
+        
+        /**
+         * @private
+         */
+        _fireMutation : function _fireMutation(evt, key, oldval, newval, obj){
+            if (newval !== oldval){
+                var data = {
+                    'key'    : key,
+                    'oldval' : oldval,
+                    'newval' : newval,
+                    'obj'    : obj,
+                    'root'   : this
+                };
+                this.fire(evt+':'+key, this, data);
+                this.fire(evt, this, data);
+            }
+            return this;
+        },
+        
+        /**
+         * Recursively iterates the hierarchy of the Config, depth-first.
+         */
+        reduce : function reduce(fn, acc, context){
+            context = context || this;
+            
+            var ret = Y.reduce(this._o, this._reducer, {
+                'acc'  : acc,
+                'path' : new Y.YArray(),
+                'cxt'  : context
+            }, this);
+            
+            return ret.acc;
+        },
+        
+        /**
+         * @private
+         * XXX: Not going to call the iterator on root objects. ok?
+         * XXX: Always passing the Config object as obj to iterator. ok?
+         */
+        _reducer : function _reducer(state, v, k, o){
+            var chain = state.path.push(k).join('.');
+            
+            // Nested object -- recurse
+            if ( Y.isPlainObject(v) )
+                state = Y.reduce(v, this._reducer, state, this);
+                
+            // Normal value -- invoke iterator
+            else
+                state.acc = fn.call(state.cxt, state.acc, v, chain, this);
+            
+            state.path.pop();
+            return state;
+        }
+        
+    });
     
 });
\ No newline at end of file
index 2a137d9..6488545 100644 (file)
--- a/tanks.php
+++ b/tanks.php
@@ -22,6 +22,8 @@ class Tanks {
         "src/tanks/config.js",
         "src/tanks/calc.js",
         
+        "src/tanks/util/config.js",
+        
         "src/tanks/thing/thing.js",
         "src/tanks/thing/bullet.js",
         "src/tanks/thing/tank.js",
@@ -35,7 +37,7 @@ class Tanks {
         
         "src/tanks/ui/ui.js",
         "src/tanks/ui/grid.js",
-        "src/tanks/ui/config.js",
+        "src/tanks/ui/ui-config.js",
         
         "src/tanks/game.js"
     );
@@ -61,26 +63,26 @@ class Tanks {
         
         "src/evt/evt.class.js",
         
-        "src/portal/math/math.js",
-        "src/portal/math/vec.js",
-        "src/portal/math/line.js",
+        "src/easel/math/math.js",
+        "src/easel/math/vec.js",
+        "src/easel/math/line.js",
         
-        "src/portal/layer.js",
-        "src/portal/shape/shape.js",
-        "src/portal/shape/circle.js",
-        "src/portal/shape/rect.js",
-        "src/portal/shape/line.js",
-        "src/portal/shape/polygon.js",
+        "src/easel/layer.js",
+        "src/easel/shape/shape.js",
+        "src/easel/shape/circle.js",
+        "src/easel/shape/rect.js",
+        "src/easel/shape/line.js",
+        "src/easel/shape/polygon.js",
         
-        "src/portal/util/binaryheap.js",
-        "src/portal/util/graph.js",
-        "src/portal/util/astar.js",
+        "src/easel/util/binaryheap.js",
+        "src/easel/util/graph.js",
+        "src/easel/util/astar.js",
         
-        "src/portal/util/tree/quadtree.js",
+        "src/easel/util/tree/quadtree.js",
         
-        "src/portal/loop/eventloop.js",
-        "src/portal/loop/fps.js",
-        "src/portal/loop/cooldown.js",
+        "src/easel/loop/eventloop.js",
+        "src/easel/loop/fps.js",
+        "src/easel/loop/cooldown.js",
     );
     
     static function writeTags($scripts=null, $prefix="") {
diff --git a/test/jsc/bar.js b/test/jsc/bar.js
new file mode 100644 (file)
index 0000000..38eaaa4
--- /dev/null
@@ -0,0 +1,3 @@
+require('./lol.js');
+require('./baz.js');
+var bar = true;
\ No newline at end of file
diff --git a/test/jsc/baz.js b/test/jsc/baz.js
new file mode 100644 (file)
index 0000000..d12b446
--- /dev/null
@@ -0,0 +1,2 @@
+require('./lol.js');
+var baz = true;
\ No newline at end of file
diff --git a/test/jsc/foo.js b/test/jsc/foo.js
new file mode 100644 (file)
index 0000000..514e202
--- /dev/null
@@ -0,0 +1,2 @@
+require('./bar.js');
+var foo = true;
\ No newline at end of file
diff --git a/test/jsc/jsc.py b/test/jsc/jsc.py
new file mode 100755 (executable)
index 0000000..0775b8d
--- /dev/null
@@ -0,0 +1,198 @@
+#!/usr/bin/env python
+# encoding: utf-8
+__author__    = 'David Schoonover <dsc@less.ly>'
+__date__      = '2010-11-24'
+__version__   = (0, 0, 1)
+
+__all__ = ('ResolutionError', 'Module', 'JSResolver',)
+
+
+import sys, re
+from itertools import chain, repeat
+from collections import defaultdict
+from subprocess import Popen, check_output, STDOUT, PIPE
+from path import path
+
+
+class ResolutionError(Exception): pass
+
+REQUIRE_PAT = re.compile(r'require\(\s*([\'"])(.*?)\1\s*\)')
+CWD = path('.')
+BLANK = path('')
+
+class Module(object):
+    module_paths = []
+    
+    @staticmethod
+    def canonicalise(module, basefile=BLANK):
+        if isinstance(basefile, Module):
+            basefile = path(basefile.key)
+        
+        if module.endswith('/index.js'):
+            module = module[:-9]
+        elif module.endswith('.js'):
+            module = module[:-3]
+        
+        if module.startswith('./') and basefile.dirname():
+            key = ( basefile.dirname() / module ).normpath()
+            if basefile.startswith('./'):
+                key = CWD / key
+        else:
+            key = module
+        
+        return str(key)
+    
+    
+    key = ''
+    query = ''
+    basefile = BLANK
+    filepath = BLANK
+    _contents = None
+    _requires = None
+    
+    
+    def __init__(self, query, basefile=BLANK):
+        if isinstance(basefile, Module):
+            basefile = basefile.key
+        
+        self.query = query
+        self.basefile = path(basefile)
+        self.key = Module.canonicalise(query, self.basefile)
+        
+        self.filepath = self.lookup()
+    
+    def lookup(self):
+        if self.key.startswith('./'):
+            search = [ CWD ]
+        else:
+            search = self.module_paths
+        for f in chain(*( (p+'.js', p/'index.js') for p in ((base / self.key).abspath() for base in search) )):
+            if f.isfile():
+                return f
+        raise ResolutionError('Unable to find %s' % self.key)
+    
+    @property
+    def contents(self):
+        if self._contents is None:
+            with self.filepath.open('rU') as f:
+                self._contents = f.read()
+        return self._contents
+    
+    @property
+    def requires(self):
+        if self._requires is None:
+            self._requires = [ Module.canonicalise(m.group(2), self) for m in REQUIRE_PAT.finditer(self.contents) ]
+        return self._requires
+    
+    def __hash__(self):
+        return hash(self.key)
+    
+    def __cmp__(self, other):
+        return cmp(self.key, other.key)
+    
+    def __eq__(self, other):
+        return self.key == other.key
+    
+    # def __str__(self):
+    #     return str(self.key)
+    
+    def __repr__(self):
+        return '{self.__class__.__name__}(key={self.key!r}, query={self.query!r}, basefile={self.basefile!r})'.format(**locals())
+
+
+
+
+class JSResolver(object):
+    """ Resolves JS imports. """
+    
+    def __init__(self, files, module_paths=None):
+        Module.module_paths = set( path(p).abspath() for p in (module_paths or []) )
+        
+        self.graph   = defaultdict(set)
+        self.files   = [ Module(CWD / path(f).normpath()) for f in files ]
+        self.modules = dict( (mod.key,mod) for mod in self.files )
+    
+    def run(self):
+        queue = self.files[:]
+        
+        while queue:
+            mod = queue.pop(0)
+            
+            for req in mod.requires:
+                
+                if req in self.modules:
+                    m = self.modules[req]
+                else:
+                    m = Module(req, mod)
+                    self.modules[req] = m
+                    queue.append(m)
+                
+                # self.graph[mod.key].add(m)
+                self.graph[m.key].add(mod)
+        
+        return self
+    
+    @property
+    def deplist(self):
+        return '\n'.join( '%s %s' % (key, dep.key) for (key, deps) in self.graph.iteritems() for dep in deps )
+    
+    @property
+    def dependencies(self):
+        # p = Process(['tsort', '-'], stdin=PIPE)
+        # p._process.stdin.write( self.deplist() )
+        # p._process.stdin.flush()
+        # return p()
+        p = Popen(['tsort'], stderr=STDOUT, stdin=PIPE, stdout=PIPE)
+        p.stdin.write( self.deplist )
+        p.stdin.close()
+        if p.wait() != 0:
+            raise ResolutionError('Cannot resolve dependencies! Requirements must be acyclic!')
+        return p.stdout.read().strip().split('\n')
+    
+    # TODO wrap in closure; collect the `module.id` info; assign `exports` to the module definition
+    # TODO create a modules repo for handling require() calls at runtime
+    
+    def cat(self):
+        for dep in self.dependencies:
+            print '// %s' % dep
+            print self.modules[dep].contents
+    
+
+
+
+
+
+def main():
+    from optparse import OptionParser
+    
+    parser = OptionParser(
+        usage   = 'usage: %prog [options] file[...] [-- [module_path[...]]]', 
+        description = 'Resolves imports in JS files',
+        version = '%prog'+" %i.%i.%i" % __version__)
+    parser.add_option("-p", "--module-paths", default='',
+        help="Comma-seperated paths to search for unqualified modules.")
+    
+    (options, args) = parser.parse_args()
+    
+    if not args:
+        parser.error("You must specify at least one JS file to resolve!")
+    
+    js = JSResolver(args, module_paths=options.module_paths.split(','))
+    js.run()
+    print 'deplist:'
+    print js.deplist
+    print
+    print 'dependencies:'
+    print '\n'.join(js.dependencies)
+    # print js.dependencies
+    print
+    print 'cat:'
+    js.cat()
+    print '---'
+    print
+    
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
+
diff --git a/test/jsc/lol.js b/test/jsc/lol.js
new file mode 100644 (file)
index 0000000..832976f
--- /dev/null
@@ -0,0 +1 @@
+var lol = true;
index a96d3a3..830a32c 100644 (file)
@@ -2,9 +2,6 @@
 <html>
 <head>
 <title>math</title>
-<?php
-//<script type="text/javascript">document.write('<base href="http://'+window.location.host+'/">');</script>
-?>
 <link rel="stylesheet" href="../../css/reset.css" type="text/css" media="screen">
 <style type="text/css" media="screen">
 
diff --git a/test/uki/index.php b/test/uki/index.php
new file mode 100644 (file)
index 0000000..d71bc76
--- /dev/null
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>uki test</title>
+<link rel="stylesheet" href="../../css/reset.css" type="text/css" media="screen">
+<style type="text/css" media="screen">
+
+html { background-color:#3F3F3F; }
+body { position:absolute; width:100%; top:0; left:0; margin:0; padding:0;
+    font-family:Geogrotesque,Helvetica; font-size:12pt; color:#fff; }
+html, body { width:100%; height:100%;
+    font:12pt "Politica Light",Arial,Helvetica,sans-serif; color:#333; background-color:#7F7F7F; }
+h1 { position:fixed; top:0; right:0; margin:0; padding:0; font-size:3em; color:#000; opacity:0.25; z-index:100; }
+/* ul, ol, li { list-style: none ! important; margin:0; padding:0; } */
+
+#content { position:relative; width:1000px; margin:1em auto; }
+/* #content > div { margin:1em 0; } */
+
+#plot { width:1000px; height:600px; background-color:#fff; }
+/* #plot .circle { z-index:5; } */
+
+#info { position:absolute; top:0; left:1000px; margin-left:0.5em; width:275px; padding:0.5em;
+    color:#ddd; background-color:rgba(0,0,0, 0.25); line-height:2em; }
+#info fieldset { border:1px solid #444; padding:0.5em; }
+#info legend { color:#ccc; font-weight:bold; padding:0 0.5em; }
+#info input { width:3em; font-size:0.8em; padding:0.1em; text-align:center; background-color:rgba(0,0,0, 0.2); color:#ccc; border:0; }
+#info label { margin-right:1em; }
+#info pre { padding:0.5em; height:5em; font:12px monospace; background-color:rgba(0,0,0, 0.2); color:#ccc; border:0; }
+
+
+#howto { position:relative; width:600px; margin:1em auto; background-color:#D8D8D8; }
+#howto > * { padding:1em; margin-top:0; margin-bottom:0; }
+#howto h3 { margin:0; padding-bottom:0; }
+
+.handle { cursor:move; }
+.rounded { border-radius:1em; -moz-border-radius:1em; -webkit-border-radius:1em; }
+
+</style>
+</head>
+<body>
+<div id="content">
+    <div id="plot"></div>
+    
+    <div id="info" class="rounded">
+        <form id="line" action="#" method="get">
+        <!--<fieldset id="line"><legend>Line</legend>-->
+            <label for="line_coords">Line:</label>
+            ( <input id="line_coords" name="x1" value="" type="text">, <input name="y1" value="" type="text"> )
+            ( <input name="x2" value="" type="text">, <input name="y2" value="" type="text"> )
+        <!--</fieldset>-->
+        </form>
+        <pre id="inter"></pre>
+    </div>
+    
+    <div id="howto" class="rounded">
+        <h3>Trigonometry and Reflection Test</h3>
+        <ul>
+            <li>The main line is pink (and the light-orange line is its tangent).</li>
+            <li>Click anywhere to add a point. It will be purple, and its reflection in the main line will be orange.</li>
+            <li>You can drag the blue control points of the main line to change it.</li>
+        </ul>
+    </div>
+</div>
+
+<div id="scripts">
+<?php
+
+require "../../tanks.php";
+Tanks::writeTags(null, "../../");
+
+$scripts = array(
+    "math.test.js"
+);
+foreach ($scripts as $s) js($s);
+
+?>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/uki/test-uki.js b/test/uki/test-uki.js
new file mode 100644 (file)
index 0000000..e69de29