goat 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/goat.rb CHANGED
@@ -6,7 +6,7 @@ require 'thin'
6
6
  require 'term/ansicolor'
7
7
  require 'tilt'
8
8
 
9
- %w{logger notifications extn html}.each do |file|
9
+ %w{logger notifications extn html static autobind}.each do |file|
10
10
  require File.join(File.dirname(__FILE__), 'goat', file)
11
11
  end
12
12
 
@@ -50,6 +50,29 @@ class Object
50
50
  end
51
51
  end
52
52
 
53
+ module Kernel
54
+ # from http://redcorundum.blogspot.com/2006/05/kernelqualifiedconstget.html
55
+ def fetch_class(str)
56
+ path = str.to_s.split('::')
57
+ from_root = path[0].empty?
58
+ if from_root
59
+ from_root = []
60
+ path = path[1..-1]
61
+ else
62
+ start_ns = ((Class === self)||(Module === self)) ? self : self.class
63
+ from_root = start_ns.to_s.split('::')
64
+ end
65
+ until from_root.empty?
66
+ begin
67
+ return (from_root+path).inject(Object) { |ns,name| ns.const_get(name) }
68
+ rescue NameError
69
+ from_root.delete_at(-1)
70
+ end
71
+ end
72
+ path.inject(Object) { |ns,name| ns.const_get(name) }
73
+ end
74
+ end
75
+
53
76
  class Set
54
77
  def glimpse(n=100)
55
78
  "#<Set: #{self.map{|x| x.glimpse(n)}.join(', ')}>"
@@ -101,11 +124,11 @@ module Goat
101
124
  end
102
125
 
103
126
  def self.load_all(dir_fragment)
104
- dir = File.join(File.dirname($0), dir_fragment)
127
+ dir = File.join(Goat.setting!(:root), dir_fragment)
105
128
  if File.directory?(dir)
106
129
  Dir.entries(dir).select{|f| f =~ /\.rb$/}.each {|f| require(File.join(dir, f))}
107
130
  end
108
- end
131
+ end
109
132
 
110
133
  @settings = {}
111
134
  def self.setting(opt)
@@ -135,6 +158,10 @@ module Goat
135
158
  Goat.extend_mongo if Goat.setting(:mongo)
136
159
  Goat.extend_sinatra if Goat.setting(:sinatra)
137
160
 
161
+ if p = Goat.setting(:press)
162
+ Goat::Static.press = p
163
+ end
164
+
138
165
  if Goat.setting(:debug)
139
166
  if defined?(Thin)
140
167
  Thin::Logging.debug = true
@@ -147,7 +174,7 @@ module Goat
147
174
  end
148
175
 
149
176
  def self.rack_builder(app)
150
- Rack::Builder.new do
177
+ Rack::Builder.new do
151
178
  if cookies = Goat.setting(:cookies)
152
179
  use Rack::Session::Cookie, cookies
153
180
  end
@@ -156,9 +183,12 @@ module Goat
156
183
  use Rack::Static, :urls => static.fetch(:urls), :root => static.fetch(:root)
157
184
  end
158
185
 
159
- use Rack::CommonLogger
160
186
  use Rack::Flash if defined?(Rack::Flash) # TODO hack
161
187
 
188
+ if Goat.setting(:reload_files)
189
+ use Rack::Reloader
190
+ end
191
+
162
192
  run app
163
193
  end
164
194
  end
@@ -347,14 +377,62 @@ module Goat
347
377
  end
348
378
  end
349
379
 
350
- module ERBHelper
380
+ module HTMLHelpers
381
+ include Rack::Utils
382
+ alias_method :h, :escape_html
383
+ alias_method :e, :escape # for URIs
384
+
385
+ def jsesc(x); x.gsub('\\', '\\\\\\').gsub('"', '\"'); end
386
+ end
387
+
388
+ module FlashHelper
389
+ def flash
390
+ request.env['x-rack.flash']
391
+ end
392
+ end
393
+
394
+ class ERBRunner
395
+ include HTMLHelpers
396
+ include FlashHelper
397
+
398
+ def initialize(req, resp, params)
399
+ @request = req
400
+ @response = resp
401
+ @params = params
402
+ end
403
+
404
+ attr_reader :request, :response, :params
405
+ attr_accessor :delegate
406
+
407
+ def take_delegate_ivars
408
+ @delegate.instance_variables.each do |ivar|
409
+ unless self.instance_variables.include?(ivar)
410
+ self.instance_variable_set(ivar, @delegate.instance_variable_get(ivar))
411
+ end
412
+ end
413
+ end
414
+
415
+ def method_missing(meth, *args)
416
+ if @delegate
417
+ @delegate.send(meth, *args)
418
+ else
419
+ super
420
+ end
421
+ end
422
+
351
423
  def erb(name, opts={}, &blk)
424
+ take_delegate_ivars if @delegate
425
+
352
426
  opts = {
353
427
  :partial => false,
354
428
  :layout => true,
355
- :locals => self.kind_of?(Page) ? {:page => self} : {}
429
+ :locals => {}
356
430
  }.merge(opts)
357
431
 
432
+ if self.kind_of?(Page)
433
+ opts[:locals][:page] ||= self
434
+ end
435
+
358
436
  partial = opts[:partial]
359
437
  use_layout = opts[:layout]
360
438
  locals = opts[:locals]
@@ -392,8 +470,8 @@ module Goat
392
470
  def partial_erb(name, opts={})
393
471
  erb(name, {:partial => true}.merge(opts))
394
472
  end
395
- end
396
-
473
+ end
474
+
397
475
  module AppHelpers
398
476
  def halt
399
477
  raise Halt.new(response)
@@ -413,13 +491,7 @@ module Goat
413
491
  c.processed_html
414
492
  end
415
493
  end
416
-
417
- module FlashHelper
418
- def flash
419
- request.env['x-rack.flash']
420
- end
421
- end
422
-
494
+
423
495
  class IndifferentHash < Hash
424
496
  def self.from_hash(hash)
425
497
  ih = self.new
@@ -442,14 +514,6 @@ module Goat
442
514
  end
443
515
  end
444
516
 
445
- module HTMLHelpers
446
- include Rack::Utils
447
- alias_method :h, :escape_html
448
- alias_method :e, :escape # for URIs
449
-
450
- def jsesc(x); x.gsub('\\', '\\\\\\').gsub('"', '\"'); end
451
- end
452
-
453
517
  class App
454
518
  class << self
455
519
 
@@ -458,7 +522,7 @@ module Goat
458
522
  @@not_found_handler = nil
459
523
  @@active_pages = {}
460
524
  @@active_page_queue = Queue.new
461
- @@before_handlers = []
525
+ @@before_handlers = []
462
526
 
463
527
  def active_pages; @@active_pages; end
464
528
  def active_page_queue; @@active_page_queue; end
@@ -587,6 +651,24 @@ module Goat
587
651
  end
588
652
  end
589
653
 
654
+ def handle_rpc(app)
655
+ req = app.request
656
+
657
+ c = req['_component']
658
+ id = req['_id']
659
+ rpc = req['_rpc']
660
+
661
+ if comp = Goat.rpc_handlers[c]
662
+ if comp[rpc]
663
+ resp = app.respond_with_hook("rpc_#{rpc}", req.params)
664
+ resp[2] = resp[2].to_json
665
+ return resp
666
+ end
667
+ end
668
+
669
+ [500, {}, {'success' => false}.to_json]
670
+ end
671
+
590
672
  def error(&blk)
591
673
  @@error_handler = blk
592
674
  end
@@ -600,7 +682,7 @@ module Goat
600
682
  def not_found_handler; @@not_found_handler; end
601
683
 
602
684
  end # end class << self
603
-
685
+
604
686
  def self.call(env)
605
687
  # TODO better place to put this?
606
688
  NotificationCenter.init # will do nothing if not enabled
@@ -610,12 +692,10 @@ module Goat
610
692
  self.req_handler.handle_request(env)
611
693
  end
612
694
 
613
- include ERBHelper
614
- include FlashHelper
615
695
  include AppHelpers
616
696
 
617
- def self.bind(hook)
618
- mname = String.random
697
+ def self.bind(hook, name=nil)
698
+ mname = name || String.random
619
699
 
620
700
  lambda do |app, *args|
621
701
  kls = app.class
@@ -647,9 +727,16 @@ module Goat
647
727
  response.finish
648
728
  end
649
729
 
730
+ def erb(name, opts={}, &blk)
731
+ e = ERBRunner.new(@req, @response, @params)
732
+ e.delegate = self
733
+ e.erb(name, {:locals => {:page => nil}}.merge(opts), &blk)
734
+ end
735
+
650
736
  map :path => '/channel', :metal => true, :hook => lambda {|app| handle_channel(app.request)}
651
737
  map :path => '/post', :metal => true, :hook => lambda {|app| handle_post(app.request) }
652
738
  map :path => '/dispatch', :metal => true, :hook => lambda {|app| handle_dispatch(app.request) }
739
+ map :path => '/rpc', :method => :post, :metal => true, :hook => lambda {|app| handle_rpc(app)}
653
740
  end
654
741
 
655
742
  class Channel
@@ -666,6 +753,8 @@ module Goat
666
753
  @emchannel.push(msg)
667
754
  end
668
755
  end
756
+
757
+ def self.rpc_handlers; @rpc_handlers ||= {}; end
669
758
 
670
759
  class Page
671
760
  attr_reader :canvas, :request, :id, :canvas, :layout, :params, :channel
@@ -676,14 +765,12 @@ module Goat
676
765
  def self.enable_live_updates; @live_updates = true; end
677
766
  def self.live_updates_enabled?; @live_updates != false; end
678
767
 
679
- include ERBHelper
680
- include HTMLHelpers
681
768
  include FlashHelper
682
769
 
683
- alias_method :old_erb, :erb
684
-
685
770
  def erb(name, opts={}, &blk)
686
- old_erb(name, {:partial => false}.merge(opts), &blk)
771
+ e = ERBRunner.new(@request, @response, @params)
772
+ e.delegate = self
773
+ e.erb(name, {:partial => false}.merge(opts), &blk)
687
774
  end
688
775
 
689
776
  def self.handle_request(app)
@@ -740,31 +827,89 @@ module Goat
740
827
  components.each(&:mark_alive!)
741
828
  end
742
829
 
830
+ class CanvasDistiller
831
+ def initialize(canvas)
832
+ @canvas = canvas
833
+ end
834
+
835
+ def all_component_classes
836
+ comp_classes = Set.new
837
+ @canvas.all_components.map(&:class).uniq.map do |cls|
838
+ c = cls
839
+
840
+ while c
841
+ if c == Object
842
+ raise "Error traversing class hierarchy to find necessary JS"
843
+ end
844
+
845
+ comp_classes << c
846
+
847
+ if c == Component
848
+ break
849
+ else
850
+ c = c.superclass
851
+ end
852
+ end
853
+ end
854
+
855
+ comp_classes
856
+ end
857
+
858
+ def script
859
+ cs = all_component_classes.to_a.reject do |cls|
860
+ Goat.setting(:press) && Goat::Static.pressed?(cls)
861
+ end
862
+
863
+ i = 0
864
+ # invariant: left of i has no superclasses to right
865
+ while i < cs.size
866
+ c = cs[i]
867
+ if cs[(i+1)..-1].any?{|sup| c.kind_of?(sup)}
868
+ cs.delete(c)
869
+ cs << c
870
+ else
871
+ i += 1
872
+ end
873
+ end
874
+
875
+ [
876
+ cs.map(&:__script),
877
+ @canvas.all_components.select{|c| c.class.wired?}.map(&:wire_script),
878
+ @canvas.all_components.map(&:__script)
879
+ ].flatten.compact.join(';')
880
+ end
881
+
882
+ def style
883
+ @canvas.all_components.map(&:class).uniq.select(&:__css).map(&:scoped_css).join
884
+ end
885
+ end
886
+
743
887
  def html
744
888
  layout = self.layout
745
889
 
746
- if @canvas.erb
747
- body = self.erb(@canvas.erb, :layout => false)
748
- style = @canvas.components.map(&:processed_css)
749
- html = "<style>#{style}</style>#{body}"
750
-
751
- render_to_layout(html, layout)
752
- elsif !canvas.body.empty?
753
- body = @canvas.body.map do |item|
890
+ distiller = CanvasDistiller.new(@canvas)
891
+ html = nil
892
+
893
+ if @canvas.erb
894
+ html = self.erb(@canvas.erb, :layout => false)
895
+ elsif !@canvas.body.empty?
896
+ html = (@canvas.body.map do |item|
754
897
  if item.kind_of?(Component)
755
- "<style>#{item.processed_css}</style>" +
756
898
  item.processed_html
757
899
  elsif item.kind_of?(String)
758
900
  item
759
901
  else
760
902
  raise "Unknown body item: #{item.inspect}"
761
903
  end
762
- end
763
-
764
- render_to_layout(body.join, layout)
904
+ end).join
765
905
  else
766
906
  raise "You can define an erb or append to the body, but not both"
767
907
  end
908
+
909
+ @canvas.style << distiller.style
910
+ @canvas.script << distiller.script
911
+
912
+ render_to_layout(html, layout)
768
913
  end
769
914
 
770
915
  def render_component(c)
@@ -789,19 +934,21 @@ module Goat
789
934
  end
790
935
 
791
936
  class PageCanvas
792
- attr_accessor :body, :title, :components, :erb
937
+ attr_accessor :body, :title, :components, :erb, :script, :style
793
938
 
794
939
  def initialize
795
940
  @body = []
796
941
  @components = []
942
+ @script = []
943
+ @style = []
797
944
  end
798
945
 
799
- def components
800
- if !body.empty?
801
- @body.select{|c| c.kind_of?(Component)}.map(&:all_components).flatten
802
- else
803
- @components
804
- end
946
+ def flattened_style; @style.join; end
947
+ def flattened_script; @script.join; end
948
+
949
+ def all_components
950
+ cs = body.empty? ? @components : @body.select{|c| c.kind_of?(Component)}
951
+ cs.map(&:all_components).flatten
805
952
  end
806
953
  end
807
954
 
@@ -809,9 +956,8 @@ module Goat
809
956
  attr_reader :id, :handlers, :page, :params
810
957
 
811
958
  include HTMLHelpers # sure just shit it in with everything else
812
- include ERBHelper
813
959
 
814
- def initialize(page)
960
+ def initialize(page)
815
961
  @id = String.random(10, :alpha => true)
816
962
  @page = page
817
963
  @callbacks = {}
@@ -819,6 +965,14 @@ module Goat
819
965
  @dead = false
820
966
  end
821
967
 
968
+ def erb(*args, &blk)
969
+ ERBRunner.new(@page.request, nil, @params).erb(*args, &blk)
970
+ end
971
+
972
+ def partial_erb(*args, &blk)
973
+ ERBRunner.new(@page.request, nil, @params).partial_erb(*args, &blk)
974
+ end
975
+
822
976
  def halt; @page.halt; end
823
977
 
824
978
  def channel; @page.channel; end
@@ -871,35 +1025,72 @@ module Goat
871
1025
  c = @callbacks[k].call
872
1026
  c[:target].send(c[:method], *c[:args])
873
1027
  end
874
-
875
- def js
876
- <<-EOJ
877
- $(document).ready(function(){
878
- Goat.registerComponent('#{id}')
879
- })
880
- EOJ
1028
+
1029
+ def self.script(script); @script = AutoBind.process(script); end
1030
+ def script(script); @script = AutoBind.process(script); end
1031
+ def self.__script; @script; end
1032
+ def __script; @script; end
1033
+
1034
+ def wire_script
1035
+ "Goat.wireComponent('#{id}', #{clientside_instance})"
881
1036
  end
882
-
1037
+
1038
+ def self.css(css); @css = css; end
1039
+ def css(css); @css = css; end
1040
+ def self.__css; @css; end
1041
+ def __css; @css; end
1042
+
1043
+ def self.clientside(js)
1044
+ script(js)
1045
+ @wired = true
1046
+ end
1047
+
1048
+ def self.wired?; @wired; end
1049
+
1050
+ def clientside_args; []; end
1051
+
1052
+ def clientside_instance
1053
+ args = [id, *clientside_args].to_json[1..-2]
1054
+ "new #{self.class.name}('#{self.class.name}', #{args})"
1055
+ end
1056
+
1057
+ def self.rpc(name, &blk)
1058
+ Goat.rpc_handlers[self.name.to_s] ||= {}
1059
+ Goat.rpc_handlers[self.name.to_s][name.to_s] = true
1060
+
1061
+ App.send(:define_method, "rpc_#{name}", blk)
1062
+ end
1063
+
883
1064
  def component(body)
884
- [:div, {:id => id},
885
- [:script, js],
886
- body
887
- ]
1065
+ [:div, {:id => id, :class => self.class.name}, body]
888
1066
  end
889
1067
 
890
1068
  def html; make_me; end
891
- def css; ''; end
892
1069
 
893
1070
  def processed_html
894
- HTMLBuilder.new(self, component(self.html)).html
1071
+ HTMLBuilder.new(self, component(self.html)).html + \
1072
+ (__css ? "<style>#{scoped_css}</style>" : '')
895
1073
  end
896
-
897
- def processed_css
898
- css.gsub(/%(\w*)([\ \t\{])/) do |str|
899
- m = $1
900
- ws = $2
901
- "#{@id}#{m.empty? ? '' : "_#{m}"}#{ws}"
1074
+
1075
+ def self.scope_css(css, prefix, dot_or_hash)
1076
+ # #%foo, .%bar, etc
1077
+ rep = css.gsub(/(.)%(\w+)([\ \t\{])/) do |str|
1078
+ p = $1
1079
+ m = $2
1080
+ ws = $3
1081
+ "#{p}#{prefix}_#{m}#{ws}"
902
1082
  end
1083
+ rep.gsub(/(^|\W)%([\W\{])/) do |str|
1084
+ "#{$1}#{dot_or_hash}#{prefix}#{$2}"
1085
+ end
1086
+ end
1087
+
1088
+ def self.scoped_css
1089
+ scope_css(self.__css, self.name, '.')
1090
+ end
1091
+
1092
+ def scoped_css
1093
+ self.class.scope_css(self.__css, @id, '#')
903
1094
  end
904
1095
 
905
1096
  def model_changed(item, notif)
@@ -916,9 +1107,17 @@ module Goat
916
1107
  # end
917
1108
  end
918
1109
 
1110
+ def children; []; end
1111
+
919
1112
  def all_components
920
- [self]
1113
+ [self] + self.children
1114
+ end
1115
+
1116
+ def to_html(builder, comp)
1117
+ processed_html # for html.rb
921
1118
  end
1119
+
1120
+ script File.read(File.join(File.dirname(__FILE__), 'goat/js/component.js'))
922
1121
  end
923
1122
 
924
1123
  class Model
data/lib/goat/goat.js CHANGED
@@ -92,7 +92,7 @@ $.extend(Goat, {
92
92
  },
93
93
 
94
94
  reopenChannel: function() {
95
- console.error("reopenChannel()");
95
+ console.log("reopenChannel()");
96
96
  var fails = this.channelOpenFails;
97
97
  setTimeout(function() { Goat.openChannel(); }, (fails > 20) ? 20000 : (fails * 500));
98
98
  this.channelOpenFails++;
@@ -160,13 +160,73 @@ $.extend(Goat, {
160
160
 
161
161
  return false;
162
162
  },
163
-
164
- registerComponent: function(id) {
165
- this.components[id] = id;
163
+
164
+ wireComponent: function(id, obj) {
165
+ this.components[id] = obj;
166
166
  }
167
167
  });
168
168
 
169
+ (function(){
170
+ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
171
+ // The base Class implementation (does nothing)
172
+ this.Class = function(){};
173
+
174
+ // Create a new Class that inherits from this class
175
+ Class.extend = function(prop) {
176
+ var _super = this.prototype;
177
+
178
+ // Instantiate a base class (but only create the instance,
179
+ // don't run the init constructor)
180
+ initializing = true;
181
+ var prototype = new this();
182
+ initializing = false;
183
+
184
+ // Copy the properties over onto the new prototype
185
+ for (var name in prop) {
186
+ // Check if we're overwriting an existing function
187
+ prototype[name] = typeof prop[name] == "function" &&
188
+ typeof _super[name] == "function" && fnTest.test(prop[name]) ?
189
+ (function(name, fn){
190
+ return function() {
191
+ var tmp = this._super;
192
+
193
+ // Add a new ._super() method that is the same method
194
+ // but on the super-class
195
+ this._super = _super[name];
196
+
197
+ // The method only need to be bound temporarily, so we
198
+ // remove it when we're done executing
199
+ var ret = fn.apply(this, arguments);
200
+ this._super = tmp;
201
+
202
+ return ret;
203
+ };
204
+ })(name, prop[name]) :
205
+ prop[name];
206
+ }
207
+
208
+ // The dummy class constructor
209
+ function Class() {
210
+ // All construction is actually done in the init method
211
+ if ( !initializing && this.init )
212
+ this.init.apply(this, arguments);
213
+ }
214
+
215
+ // Populate our constructed prototype object
216
+ Class.prototype = prototype;
217
+
218
+ // Enforce the constructor to be what we expect
219
+ Class.constructor = Class;
220
+
221
+ // And make this class extendable
222
+ Class.extend = arguments.callee;
223
+
224
+ return Class;
225
+ };
226
+ })();
227
+
169
228
  $(document).ready(function() {
229
+ $.each(Goat.components, function(_, c) { c.onLoad(); });
170
230
  setTimeout(function() { if(Goat.page_id) Goat.openChannel(); }, 1);
171
231
  });
172
232
 
@@ -19,7 +19,7 @@ module Goat
19
19
  end
20
20
 
21
21
  def unbind
22
- Logger.error :live, "Lost connection"
22
+ Logger.error :live, "Lost notification server connection"
23
23
  EM.add_timer(1) do
24
24
  NotificationCenter.start_receiver
25
25
  end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 6
10
- version: 0.1.6
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Patrick Collison
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-06 00:00:00 +00:00
18
+ date: 2010-09-20 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies: []
21
21