sproutcore 1.6.0.1-java → 1.7.1.beta-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/CHANGELOG +21 -0
  2. data/Gemfile +5 -0
  3. data/Rakefile +26 -13
  4. data/VERSION.yml +2 -2
  5. data/lib/Buildfile +43 -4
  6. data/lib/buildtasks/build.rake +10 -0
  7. data/lib/buildtasks/helpers/file_rule.rb +22 -0
  8. data/lib/buildtasks/helpers/file_rule_list.rb +137 -0
  9. data/lib/buildtasks/manifest.rake +133 -122
  10. data/lib/frameworks/sproutcore/CHANGELOG.md +69 -2
  11. data/lib/frameworks/sproutcore/apps/tests/english.lproj/strings.js +1 -0
  12. data/lib/frameworks/sproutcore/frameworks/bootstrap/system/browser.js +28 -22
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/array.js +9 -5
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/controller.js +1 -1
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/controls/button.js +18 -13
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/bind.js +5 -3
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/collection.js +2 -0
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/action_support.js +80 -0
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/template_helpers/text_field_support.js +84 -116
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +8 -5
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +157 -157
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/platform.js +5 -3
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +6 -6
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/sparse_array.js +10 -7
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +106 -0
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/collection.js +18 -0
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/handlebars.js +71 -1
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/attribute_bindings_test.js +38 -0
  29. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/class_name_bindings_test.js +47 -0
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutChildViews.js +18 -18
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +42 -10
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +158 -1
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/keyboard.js +26 -1
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +14 -8
  35. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +15 -2
  36. data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +108 -108
  37. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
  38. data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -4
  39. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/error_methods.js +2 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/single_attribute.js +26 -0
  41. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/query/builders.js +7 -0
  42. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/record_array/error_methods.js +1 -1
  43. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +4 -1
  44. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/tests/system/datetime.js +6 -0
  45. data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +26 -5
  46. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +97 -96
  47. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -3
  48. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/ui.js +17 -4
  49. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +7 -7
  50. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +7 -5
  51. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +12 -3
  52. data/lib/frameworks/sproutcore/frameworks/desktop/views/web.js +23 -14
  53. data/lib/frameworks/sproutcore/frameworks/experimental/Buildfile +5 -1
  54. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/render_delegates/menu_scroller.js +28 -0
  55. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/tests/menu/scroll.js +235 -0
  56. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroll.js +363 -0
  57. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroller.js +250 -0
  58. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/desktop_scroller.js +92 -0
  59. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/native_scroll.js +25 -0
  60. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/scroll.js +33 -0
  61. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/touch_scroller.js +76 -0
  62. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/integration.js +50 -0
  63. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/methods.js +143 -0
  64. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/ui.js +258 -0
  65. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroll.js +1164 -0
  66. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroller.js +332 -0
  67. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroll.js +236 -0
  68. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroller.js +347 -0
  69. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroll.js +15 -0
  70. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroller.js +10 -0
  71. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroll.js +804 -0
  72. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroller.js +133 -0
  73. data/lib/frameworks/sproutcore/frameworks/foundation/resources/text_field.css +3 -3
  74. data/lib/frameworks/sproutcore/frameworks/foundation/validators/number.js +3 -1
  75. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +3 -3
  76. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +2 -1
  77. data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +2 -1
  78. data/lib/frameworks/sproutcore/frameworks/media/views/media_slider.js +2 -4
  79. data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +2 -4
  80. data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +2 -4
  81. data/lib/frameworks/sproutcore/frameworks/media/views/video.js +2 -2
  82. data/lib/frameworks/sproutcore/frameworks/routing/system/routes.js +29 -3
  83. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  84. data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/replace.js +1 -1
  85. data/lib/frameworks/sproutcore/frameworks/runtime/private/property_chain.js +2 -1
  86. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +3 -3
  87. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +2 -2
  88. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +1 -1
  89. data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list_item.css +2 -2
  90. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/segmented.css +1 -1
  91. data/lib/gen/app/templates/apps/@target_name@/Buildfile +3 -5
  92. data/lib/gen/app/templates/apps/@target_name@/resources/_theme.css +18 -0
  93. data/lib/gen/project/templates/@filename@/Buildfile +2 -2
  94. data/lib/sproutcore.rb +30 -5
  95. data/lib/sproutcore/builders.rb +1 -0
  96. data/lib/sproutcore/builders/chance_file.rb +9 -16
  97. data/lib/sproutcore/builders/html.rb +2 -1
  98. data/lib/sproutcore/builders/minify.rb +4 -35
  99. data/lib/sproutcore/builders/module.rb +38 -1
  100. data/lib/sproutcore/builders/split.rb +63 -0
  101. data/lib/sproutcore/builders/strings.rb +7 -1
  102. data/lib/sproutcore/helpers.rb +1 -1
  103. data/lib/sproutcore/helpers/css_split.rb +190 -0
  104. data/lib/sproutcore/helpers/entry_sorter.rb +2 -0
  105. data/lib/sproutcore/helpers/minifier.rb +40 -16
  106. data/lib/sproutcore/helpers/static_helper.rb +35 -17
  107. data/lib/sproutcore/models/manifest.rb +26 -0
  108. data/lib/sproutcore/models/target.rb +12 -1
  109. data/lib/sproutcore/rack.rb +1 -0
  110. data/lib/sproutcore/rack/proxy.rb +244 -225
  111. data/lib/sproutcore/rack/restrict_ip.rb +67 -0
  112. data/lib/sproutcore/rack/service.rb +8 -2
  113. data/lib/sproutcore/tools.rb +102 -46
  114. data/lib/sproutcore/tools/build.rb +91 -43
  115. data/lib/sproutcore/tools/gen.rb +2 -3
  116. data/lib/sproutcore/tools/manifest.rb +22 -16
  117. data/lib/sproutcore/tools/server.rb +21 -0
  118. data/spec/buildtasks/helpers/accept_list +22 -0
  119. data/spec/buildtasks/helpers/accept_list.rb +128 -0
  120. data/spec/buildtasks/helpers/list.json +11 -0
  121. data/spec/buildtasks/manifest/prepare_build_tasks/chance_2x_spec.rb +1 -39
  122. data/spec/buildtasks/manifest/prepare_build_tasks/chance_spec.rb +0 -38
  123. data/spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb +4 -4
  124. data/spec/buildtasks/manifest/prepare_build_tasks/module_spec.rb +2 -2
  125. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_indirect_spec.rb +7 -16
  126. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_spec.rb +7 -17
  127. data/spec/buildtasks/manifest/prepare_build_tasks/packed_spec.rb +11 -6
  128. data/spec/fixtures/builder_tests/Buildfile +2 -1
  129. data/spec/fixtures/builder_tests/apps/module_test/modules/required_module/core.js +0 -0
  130. data/spec/lib/builders/module_spec.rb +1 -1
  131. data/spec/spec_helper.rb +1 -0
  132. data/sproutcore.gemspec +4 -9
  133. data/vendor/chance/lib/chance.rb +25 -6
  134. data/vendor/chance/lib/chance/factory.rb +45 -0
  135. data/vendor/chance/lib/chance/instance.rb +173 -28
  136. data/vendor/chance/lib/chance/instance/data_url.rb +0 -29
  137. data/vendor/chance/lib/chance/instance/slicing.rb +57 -4
  138. data/vendor/chance/lib/chance/instance/spriting.rb +112 -21
  139. data/vendor/chance/lib/chance/parser.rb +80 -52
  140. data/vendor/sproutcore/SCCompiler.jar +0 -0
  141. data/vendor/sproutcore/lib/args4j-2.0.12.jar +0 -0
  142. data/vendor/sproutcore/lib/yuicompressor-2.4.2.jar +0 -0
  143. metadata +84 -25
@@ -111,7 +111,13 @@ describe "manifest:prepare_build_tasks:packed" do
111
111
 
112
112
  before do
113
113
  run_task
114
- @entry = entry_for('stylesheet-packed.css')
114
+
115
+ # for 1x (but not 2x) there should be an intermediary entry for a transform
116
+ # (split_css) that... splits the CSS. See its documentation.
117
+ #
118
+ # we are looking for the stylesheet-packed.css that is set on its source entry.
119
+ @entry = entry_for('stylesheet-packed.css')[:source_entry]
120
+
115
121
  @entry_2x = entry_for('stylesheet@2x-packed.css')
116
122
  end
117
123
 
@@ -125,11 +131,6 @@ describe "manifest:prepare_build_tasks:packed" do
125
131
  @entry.source_entries.each do |entry|
126
132
  entry.filename.should == 'stylesheet.css'
127
133
  end
128
-
129
- @entry_2x.source_entries.size.should > 0
130
- @entry_2x.source_entries.each do |entry|
131
- entry.filename.should == 'stylesheet@2x.css'
132
- end
133
134
  end
134
135
 
135
136
  it "should include ordered_entries ordered by required target order" do
@@ -145,17 +146,6 @@ describe "manifest:prepare_build_tasks:packed" do
145
146
  @entry.ordered_entries.each do |entry|
146
147
  entry.target.should == targets.shift
147
148
  end
148
-
149
- targets = @target.expand_required_targets(:theme => true) + [@target]
150
- variation = @entry.manifest.variation
151
- targets.reject! do |t|
152
- t.manifest_for(variation).build!.entry_for('stylesheet@2x.css').nil?
153
- end
154
-
155
- @entry_2x.ordered_entries.each do |entry|
156
- entry.target.should == targets.shift
157
- end
158
-
159
149
  end
160
150
 
161
151
  it "should include the actual targets this packed version covers in the targets property (even those w no stylesheet.css)" do
@@ -40,8 +40,8 @@ describe "manifest:prepare_build_tasks:packed" do
40
40
  @manifest.entry_for('javascript-packed.js').should_not be_nil
41
41
  @manifest.entry_for('stylesheet-packed.css').should_not be_nil
42
42
 
43
- # In this version, there should _not_ be an @2x entry.
44
- @manifest.entry_for('stylesheet@2x-packed.css').should be_nil
43
+ # there must always be a 2x-packed entry
44
+ @manifest.entry_for('stylesheet@2x-packed.css').should_not be_nil
45
45
  end
46
46
 
47
47
  it "should remove non-packed JS entries from apps" do
@@ -132,16 +132,21 @@ describe "manifest:prepare_build_tasks:packed" do
132
132
 
133
133
  run_task
134
134
 
135
- @entry = entry_for('stylesheet-packed.css')
135
+ # for 1x (but not 2x) there should be an intermediary entry for a transform
136
+ # (split_css) that... splits the CSS. See its documentation.
137
+ #
138
+ # we are looking for the stylesheet-packed.css that is set on its source entry.
139
+ @entry = entry_for('stylesheet-packed.css')[:source_entry]
140
+
136
141
  @entry_2x = entry_for('stylesheet@2x-packed.css')
137
142
  end
138
143
 
139
144
  it "should generate a stylesheet-packed.css entry" do
140
145
  @entry.should_not be_nil
141
146
 
142
- # should be _no_ @2x entry for this version (see packed_2x_spec)
143
- # because the @2x version is only included as-needed.
144
- @entry_2x.should be_nil
147
+ # There should always be a 2x packed entry, because default JS
148
+ # looks for 2x when running in 2x mode, and has no fallback.
149
+ @entry_2x.should_not be_nil
145
150
  end
146
151
 
147
152
  it "should include stylesheet.css entries from all required targets" do
@@ -19,4 +19,5 @@ config '/module_test/deferred_module',
19
19
  config :module_test,
20
20
  :deferred_modules => [:deferred_module],
21
21
  :prefetched_modules => [:required_target],
22
- :inlined_modules => [:inlined_module]
22
+ :inlined_modules => [:inlined_module],
23
+ :required => [:required_module]
@@ -45,7 +45,7 @@ describe 'SC::Builder::ModuleInfo' do
45
45
  req = @target.required_targets
46
46
 
47
47
  req.size.should == 1
48
- req.first.target_name.should == :'/module_test/inlined_module'
48
+ req.first.target_name.should == :'/module_test/required_module'
49
49
  end
50
50
 
51
51
  it "should require one deferred module" do
data/spec/spec_helper.rb CHANGED
@@ -2,6 +2,7 @@ require 'fileutils'
2
2
  require 'tempfile'
3
3
 
4
4
  require "sproutcore"
5
+ Dir["#{File.dirname(__FILE__)}/../lib/buildtasks/helpers/*.rb"].each {|f| require f}
5
6
 
6
7
  module SC
7
8
 
data/sproutcore.gemspec CHANGED
@@ -3,7 +3,6 @@ require "sproutcore/version"
3
3
 
4
4
  os = Gem::Platform.local.os
5
5
  is_jruby = (os == "java")
6
- is_mingw = (os == "mingw32")
7
6
 
8
7
  Gem::Specification.new do |s|
9
8
  s.name = 'sproutcore'
@@ -14,17 +13,17 @@ Gem::Specification.new do |s|
14
13
  s.summary = "SproutCore is a platform for building native look-and-feel applications on the web"
15
14
 
16
15
  s.platform = 'java' if is_jruby
17
- s.platform = 'x86-mingw32' if is_mingw
18
16
 
19
17
  s.add_dependency 'rack', '~> 1.2'
20
18
  s.add_dependency 'json_pure', "~> 1.4.6"
21
19
  s.add_dependency 'extlib', "~> 0.9.15"
22
20
  s.add_dependency 'erubis', "~> 2.6"
23
21
  s.add_dependency 'thor', '~> 0.14.3'
24
- s.add_dependency 'haml', '~> 3.1.1'
25
-
26
- s.add_dependency 'compass', '~> 0.11.1'
22
+ s.add_dependency 'sass', '~> 3.1.3'
23
+ s.add_dependency 'haml', '~> 3.1.2'
27
24
 
25
+ s.add_dependency 'compass', '~> 0.11.3'
26
+ s.add_dependency 'chunky_png', '~> 1.2.0'
28
27
  s.add_dependency 'em-http-request', '~> 1.0.0.beta'
29
28
 
30
29
  if is_jruby
@@ -33,10 +32,6 @@ Gem::Specification.new do |s|
33
32
  s.add_dependency 'thin', '~> 1.2.11'
34
33
  end
35
34
 
36
- if is_mingw
37
- s.add_dependency 'eventmachine', '~> 1.0.0.beta'
38
- end
39
-
40
35
  s.add_development_dependency 'gemcutter', "~> 0.6.0"
41
36
  s.add_development_dependency 'rspec', "~> 2.5.0"
42
37
  s.add_development_dependency 'rake'
@@ -1,4 +1,5 @@
1
1
  require "chance/instance"
2
+ require "chance/factory"
2
3
 
3
4
  require 'sass'
4
5
 
@@ -19,8 +20,10 @@ module Chance
19
20
 
20
21
  @files = {}
21
22
 
23
+ @clear_files_immediately = false
24
+
22
25
  class << self
23
- attr_accessor :files, :_current_instance
26
+ attr_accessor :files, :_current_instance, :clear_files_immediately
24
27
 
25
28
  def add_file(path, content=nil)
26
29
  mtime = 0
@@ -29,9 +32,7 @@ module Chance
29
32
  end
30
33
 
31
34
  if @files[path]
32
- mtime = File.mtime(path).to_f
33
- update_file(path, content) if mtime > @files[path][:mtime]
34
- return
35
+ return update_file_if_needed(path, content)
35
36
  end
36
37
 
37
38
  file = {
@@ -67,6 +68,21 @@ module Chance
67
68
  puts "Updated " + path if Chance::CONFIG[:verbose]
68
69
  end
69
70
 
71
+ # if the path is a valid filesystem path and the mtime has changed, this invalidates
72
+ # the file. Returns the mtime if the file was updated.
73
+ def update_file_if_needed(path, content=nil)
74
+ if @files[path]
75
+ mtime = File.mtime(path).to_f
76
+ if mtime > @files[path][:mtime]
77
+ update_file(path, content)
78
+ end
79
+
80
+ return mtime
81
+ else
82
+ return false
83
+ end
84
+ end
85
+
70
86
  def remove_file(path)
71
87
  if not @files.has_key? path
72
88
  puts "Could not remove " + path + " because it is not in system."
@@ -144,10 +160,13 @@ module Chance
144
160
  content = file[:content]
145
161
 
146
162
  requires = []
147
- content = content.gsub(/sc_require\(['"]?(.*?)['"]?\);?/) {|match|
148
- requires.push $1
163
+ content = content.gsub(/(sc_)?require\(['"]?(.*?)['"]?\);?/) {|match|
164
+ requires.push $2
149
165
  ""
150
166
  }
167
+
168
+ # sc_resource will already be handled by the build tool. We just need to ignore it.
169
+ content.gsub!(/sc_resource\(['"]?(.*?)['"]?\);?/, '')
151
170
 
152
171
  file[:requires] = requires
153
172
  file[:content] = content
@@ -0,0 +1,45 @@
1
+ #
2
+ # The Chance factory.
3
+ # All methods require two parts: a key and a hash of options. The hash will be used in
4
+ # the case of a cache missed.
5
+ #
6
+ module Chance
7
+ module ChanceFactory
8
+
9
+ @instances = {}
10
+ @file_hashes = {}
11
+ class << self
12
+ def clear_instances
13
+ @file_hashes = {}
14
+ @instances = {}
15
+ end
16
+
17
+ def instance_for_key(key, opts)
18
+ if not @instances.include? key
19
+ @instances[key] = Chance::Instance.new(opts)
20
+ end
21
+
22
+ return @instances[key]
23
+ end
24
+
25
+ # Call with a hash mapping instance paths to absolute paths. This will compare with
26
+ # the last
27
+ def update_instance(key, opts, files)
28
+ instance = instance_for_key(key, opts)
29
+ last_hash = @file_hashes[key] || {}
30
+
31
+ # If they are not equal, we might as well throw everything. The biggest cost is from
32
+ # Chance re-running, and it will have to anyway.
33
+ if not last_hash.eql? files
34
+ instance.unmap_all
35
+ files.each {|path, identifier|
36
+ instance.map_file path, identifier
37
+ }
38
+
39
+ @file_hashes[key] = files
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -10,6 +10,8 @@ require 'chance/instance/spriting'
10
10
  require 'chance/instance/data_url'
11
11
  require 'chance/instance/javascript'
12
12
 
13
+ require 'digest/md5'
14
+
13
15
 
14
16
  Compass.discover_extensions!
15
17
  Compass.configure_sass_plugin!
@@ -21,7 +23,9 @@ module Chance
21
23
  attr_reader :path
22
24
  def initialize(path)
23
25
  @path = path
26
+ super(message)
24
27
  end
28
+
25
29
  def message
26
30
  "File not mapped in Chance instance: #{path}"
27
31
  end
@@ -48,18 +52,25 @@ module Chance
48
52
  "chance@2x.css" => { :method => :css, :x2 => true },
49
53
  "chance-sprited.css" => { :method => :css, :sprited => true },
50
54
  "chance-sprited@2x.css" => { :method => :css, :sprited => true, :x2 => true },
51
- "chance.js" => { :method => :javascript },
52
- "chance-mhtml.txt" => { :method => :mhtml },
53
55
 
54
56
  # For Testing Purposes...
55
57
  "chance-test.css" => { :method => :chance_test }
56
58
  }
57
59
 
60
+ @@uid = 0
61
+
58
62
  @@generation = 0
59
63
 
60
64
  def initialize(options = {})
61
65
  @options = options
62
66
  @options[:theme] = "" if @options[:theme].nil?
67
+ @options[:pad_sprites_for_debugging] = true if @options[:pad_sprites_for_debugging].nil?
68
+ @options[:optimize_sprites] = true if @options[:optimize_sprites].nil?
69
+
70
+ @@uid += 1
71
+ @uid = @@uid
72
+
73
+ @options[:instance_id] = @uid if @options[:instance_id].nil?
63
74
 
64
75
  if @options[:theme].length > 0 and @options[:theme][0] != "."
65
76
  @options[:theme] = "." + @options[:theme].to_s
@@ -68,6 +79,10 @@ module Chance
68
79
  # The mapped files are a map from file names in the Chance Instance to
69
80
  # their identifiers in Chance itself.
70
81
  @mapped_files = { }
82
+
83
+ # The file mtimes are a collection of mtimes for all the files we have. Each time we
84
+ # read a file we record the mtime, and then we compare on check_all_files
85
+ @file_mtimes = { }
71
86
 
72
87
  # The @files set is a set cached generated output files, used by the output_for
73
88
  # method.
@@ -80,6 +95,10 @@ module Chance
80
95
 
81
96
  # Tracks whether _render has been called.
82
97
  @has_rendered = false
98
+
99
+ # A generation number for the current render. This allows the slicing and spriting
100
+ # to be invalidated smartly.
101
+ @render_cycle = 0
83
102
  end
84
103
 
85
104
  # maps a path relative to the instance to a file identifier
@@ -90,6 +109,11 @@ module Chance
90
109
  # The identifier would be a name of a file that you added to
91
110
  # Chance using add_file.
92
111
  def map_file(path, identifier)
112
+ if @mapped_files[path] == identifier
113
+ # Don't do anything if there is nothing to do.
114
+ return
115
+ end
116
+
93
117
  path = path.to_s
94
118
  file = Chance.has_file(identifier)
95
119
 
@@ -104,12 +128,35 @@ module Chance
104
128
  # unmaps a path from its identifier. In short, removes a file
105
129
  # from this Chance instance. The file will remain in Chance's "virtual filesystem".
106
130
  def unmap_file(path)
131
+ if not @mapped_files.include?(path)
132
+ # Don't do anything if there is nothing to do
133
+ return
134
+ end
135
+
107
136
  path = path.to_s
108
137
  @mapped_files.delete path
109
138
 
110
139
  # Invalidate our render because things have changed.
111
140
  clean
112
141
  end
142
+
143
+ # unmaps all files
144
+ def unmap_all
145
+ @mapped_files = {}
146
+ end
147
+
148
+ # checks all files to see if they have changed
149
+ def check_all_files
150
+ needs_clean = false
151
+ @mapped_files.each {|p, f|
152
+ mtime = Chance.update_file_if_needed(f)
153
+ if @file_mtimes[p].nil? or mtime > @file_mtimes[p]
154
+ needs_clean = true
155
+ end
156
+ }
157
+
158
+ clean if needs_clean
159
+ end
113
160
 
114
161
  # Using a path relative to this instance, gets an actual Chance file
115
162
  # hash, with any necessary preprocessing already performed. For instance,
@@ -178,6 +225,9 @@ module Chance
178
225
  def _render
179
226
  return if @has_rendered
180
227
 
228
+ # Update the render cycle to invalidate sprites, slices, etc.
229
+ @render_cycle = @render_cycle + 1
230
+
181
231
  @files = {}
182
232
  begin
183
233
  # SCSS code executing needs to know what the current instance of Chance is,
@@ -188,13 +238,25 @@ module Chance
188
238
  # The output of this process is a "virtual" file that imports all of the
189
239
  # SCSS files used by this Chance instance. This also sets up the @slices hash.
190
240
  import_css = _preprocess
241
+
242
+ # Because we encapsulate with instance_id, we should not have collisions even IF another chance
243
+ # instance were running at the same time (which it couldn't; if it were, there'd be MANY other issues)
244
+ image_css_path = File.join('./tmp/chance/image_css', @options[:instance_id].to_s, '_image_css.scss')
245
+ FileUtils.mkdir_p(File.dirname(image_css_path))
246
+
247
+ file = File.new(image_css_path, "w")
248
+ file.write(_css_for_slices)
249
+ file.close
250
+
251
+ image_css_path = File.join('./tmp/chance/image_css', @options[:instance_id].to_s, 'image_css')
252
+
191
253
 
192
254
  # STEP 2: Preparing input CSS
193
255
  # The main CSS file we pass to the Sass Engine will have placeholder CSS for the
194
256
  # slices (the details will be postprocessed out).
195
257
  # After that, all of the individual files (using the import CSS generated
196
258
  # in Step 1)
197
- css = _css_for_slices + "\n" + import_css
259
+ css = "@import \"#{image_css_path}\";\n" + import_css
198
260
 
199
261
  # Step 3: Apply Sass Engine
200
262
  engine = Sass::Engine.new(css, Compass.sass_engine_options.merge({
@@ -218,17 +280,24 @@ module Chance
218
280
  # slicing operation has not yet taken place. The postprocessing portion
219
281
  # receives sliced versions.
220
282
  def _css_for_slices
221
- output = ""
283
+
284
+ output = []
222
285
  slices = @slices
223
286
 
224
287
  slices.each do |name, slice|
225
- # so, the path should be the path in the chance instance
226
- output += "." + slice[:css_name] + " { "
227
- output += "_sc_chance: \"#{name}\";"
228
- output += "} \n"
288
+ # Write out comments specifying all the files the slice is used from
289
+ output << "/* Slice #{name}, used in: \n"
290
+ slice[:used_by].each {|used_by|
291
+ output << "\t#{used_by[:path]}\n"
292
+ }
293
+ output << "*/"
294
+
295
+ output << "." + slice[:css_name] + " { "
296
+ output << "_sc_chance: \"#{name}\";"
297
+ output << "} \n"
229
298
  end
230
299
 
231
- return output
300
+ return output.join ""
232
301
 
233
302
  end
234
303
 
@@ -241,16 +310,63 @@ module Chance
241
310
  # :sprited => whether to use spriting instead of data uris.
242
311
  def _postprocess_css(opts)
243
312
  if opts[:sprited]
244
- postprocess_css_sprited(opts)
313
+ ret = postprocess_css_sprited(opts)
245
314
  else
246
- postprocess_css_dataurl(opts)
315
+ ret = postprocess_css_dataurl(opts)
247
316
  end
317
+
318
+ ret = _strip_slice_class_names(ret)
319
+ end
320
+
321
+ #
322
+ # Strips dummy slice class names that were added by Chance so that SCSS could do its magic,
323
+ # but which are no longer needed.
324
+ #
325
+ def _strip_slice_class_names(css)
326
+ css.gsub! /\.__chance_slice[^{]*?,/, ""
327
+ css
248
328
  end
249
329
 
250
330
 
251
331
  #
252
332
  # COMBINING CSS
253
333
  #
334
+
335
+ # Determines the "Chance Header" to add at the beginning of the file. The
336
+ # Chance Header can set, for instance, the $theme variable.
337
+ #
338
+ # The Chance Header is loaded from the nearest _theme.css file in this folder
339
+ # or a containing folder (the file list specifically ignores such files; they are
340
+ # only used for this purpose)
341
+ #
342
+ # For backwards-compatibility, the fallback if no _theme.css file is present
343
+ # is to return code setting $theme to the now-deprecated @options[:theme]
344
+ # passed to Chance
345
+ def chance_header_for_file(file)
346
+ # 'file' is the name of a file, so we actually need to start at dirname(file)
347
+ dir = File.dirname(file)
348
+
349
+ # This should not be slow, as this is just a hash lookup
350
+ while dir.length > 0 and not dir == "."
351
+ header_file = @mapped_files[File.join(dir, "_theme.css")]
352
+ if not header_file.nil?
353
+ return Chance.get_file(header_file)
354
+ end
355
+
356
+ dir = File.dirname(dir)
357
+ end
358
+
359
+ # Make sure to look globally
360
+ header_file = @mapped_files["_theme.css"]
361
+ return Chance.get_file(header_file) if not header_file.nil?
362
+
363
+ {
364
+ # Never changes (without a restart, at least)
365
+ :mtime => 0,
366
+ :content => "$theme: '" + @options[:theme] + "';\n"
367
+ }
368
+ end
369
+
254
370
  #
255
371
  # _include_file is the recursive method in the depth-first-search
256
372
  # that creates the ordered list of files.
@@ -262,6 +378,9 @@ module Chance
262
378
  #
263
379
  def _include_file(file)
264
380
  return if not file =~ /\.css$/
381
+
382
+ # skip _theme.css files
383
+ return if file =~ /_theme\.css$/
265
384
 
266
385
  file = Chance.get_file(file)
267
386
 
@@ -271,7 +390,13 @@ module Chance
271
390
  requires = file[:requires]
272
391
  file[:included] = @@generation
273
392
 
274
- requires.each {|r| _include_file(@mapped_files[r]) } unless requires.nil?
393
+ if not requires.nil?
394
+ requires.each {|r|
395
+ # Add the .css extension if needed. it is optional for sc_require
396
+ r = r + ".css" if not r =~ /\.css$/
397
+ _include_file(@mapped_files[r])
398
+ }
399
+ end
275
400
 
276
401
 
277
402
 
@@ -289,35 +414,55 @@ module Chance
289
414
  @@generation = @@generation + 1
290
415
  files = @mapped_files.values
291
416
  @file_list = []
292
-
293
- files.each {|f| _include_file(f) }
417
+
418
+ # We have to sort alphabetically first...
419
+ tmp_file_list = []
420
+ @mapped_files.each {|p, f| tmp_file_list.push([p, f]) }
421
+ tmp_file_list.sort_by! {|a| a[0] }
422
+
423
+ tmp_file_list.each {|paths|
424
+ p, f = paths
425
+
426
+ # Save the mtime for caching
427
+ mtime = Chance.update_file_if_needed(f)
428
+ @file_mtimes[p] = mtime
429
+
430
+ _include_file(f)
431
+ }
294
432
 
295
433
  relative_paths = @mapped_files.invert
296
434
 
297
435
  @file_list.map {|file|
298
- # The parser accepts single files that contain many files. As such,
299
- # its method of determing the current file name is a marker in the
300
- # file. We may want to consider changing this to a parser option
301
- # now that we don't need this feature so much, but this works for now.
436
+ # NOTE: WE MUST CALL CHANCE PARSER NOW, because it generates our slicses.
437
+ # We can't be picky and just call it if something has changed. Thankfully,
438
+ # parser is fast. Unlike SCSS.
439
+ header_file = chance_header_for_file(relative_paths[file[:path]])
440
+
302
441
  content = "@_chance_file " + relative_paths[file[:path]] + ";\n"
303
- content += "$theme: '" + @options[:theme] + "';"
442
+ content += header_file[:content]
304
443
  content += file[:content]
305
444
 
306
445
  parser = Chance::Parser.new(content, @options)
307
446
  parser.parse
308
447
  file[:parsed_css] = parser.css
448
+
449
+ # We used to use an md5 hash here, but this hides the original file name
450
+ # from SCSS, which makes the file name + line number comments useless.
451
+ #
452
+ # Instead, we sanitize the path.
453
+ path_safe = file[:path].gsub(/[^a-zA-Z0-9\-_\\\/]/, '-')
309
454
 
310
- # Remove any non-letter characters that could cause a problem in filenames,
311
- # i.e. colons in drive names on Windows
312
- # Also removing leading slashes
313
- cleaned_path = file[:path].gsub(/[^\w\/_-]+/,'_').sub(/^\//,'')
314
- tmp_path = "./tmp/chance/#{cleaned_path}.scss"
455
+ tmp_path = "./tmp/chance/#{path_safe}.scss"
315
456
 
316
457
  FileUtils.mkdir_p(File.dirname(tmp_path))
317
-
318
- f = File.new(tmp_path, "w")
319
- f.write(parser.css)
320
- f.close
458
+
459
+ if (not file[:mtime] or not file[:wtime] or file[:wtime] < file[:mtime] or
460
+ not header_file[:mtime] or file[:wtime] < header_file[:mtime])
461
+ f = File.new(tmp_path, "w")
462
+ f.write(parser.css)
463
+ f.close
464
+ file[:wtime] = Time.now.to_f
465
+ end
321
466
 
322
467
  css = "@import \"" + tmp_path + "\";"
323
468