goat 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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