sproutcore 1.6.0.1 → 1.7.1.beta

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.
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/keyboard.js +26 -1
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +14 -8
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +158 -1
  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/builders/chance_file.rb +9 -16
  95. data/lib/sproutcore/builders/html.rb +2 -1
  96. data/lib/sproutcore/builders/minify.rb +4 -35
  97. data/lib/sproutcore/builders/module.rb +38 -1
  98. data/lib/sproutcore/builders/split.rb +63 -0
  99. data/lib/sproutcore/builders/strings.rb +7 -1
  100. data/lib/sproutcore/builders.rb +1 -0
  101. data/lib/sproutcore/helpers/css_split.rb +190 -0
  102. data/lib/sproutcore/helpers/entry_sorter.rb +2 -0
  103. data/lib/sproutcore/helpers/minifier.rb +40 -16
  104. data/lib/sproutcore/helpers/static_helper.rb +35 -17
  105. data/lib/sproutcore/helpers.rb +1 -1
  106. data/lib/sproutcore/models/manifest.rb +26 -0
  107. data/lib/sproutcore/models/target.rb +12 -1
  108. data/lib/sproutcore/rack/proxy.rb +244 -225
  109. data/lib/sproutcore/rack/restrict_ip.rb +67 -0
  110. data/lib/sproutcore/rack/service.rb +8 -2
  111. data/lib/sproutcore/rack.rb +1 -0
  112. data/lib/sproutcore/tools/build.rb +91 -43
  113. data/lib/sproutcore/tools/gen.rb +2 -3
  114. data/lib/sproutcore/tools/manifest.rb +22 -16
  115. data/lib/sproutcore/tools/server.rb +21 -0
  116. data/lib/sproutcore/tools.rb +102 -46
  117. data/lib/sproutcore.rb +30 -5
  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/factory.rb +45 -0
  134. data/vendor/chance/lib/chance/instance/data_url.rb +0 -29
  135. data/vendor/chance/lib/chance/instance/slicing.rb +57 -4
  136. data/vendor/chance/lib/chance/instance/spriting.rb +112 -21
  137. data/vendor/chance/lib/chance/instance.rb +173 -28
  138. data/vendor/chance/lib/chance/parser.rb +80 -52
  139. data/vendor/chance/lib/chance.rb +25 -6
  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 +97 -38
@@ -18,12 +18,12 @@ module Chance
18
18
  # images in the class's @sprites property and updating the individual slices
19
19
  # with a :sprite property containing the identifier of the sprite, and offset
20
20
  # properties for the offsets within the image.
21
- def generate_sprite_definitions(opts)
21
+ def generate_sprite_definitions(opts)
22
22
  @sprites = {}
23
23
 
24
24
  group_slices_into_sprites(opts)
25
25
  @sprites.each do |key, sprite|
26
- layout_slices_in_sprite sprite
26
+ layout_slices_in_sprite sprite, opts
27
27
  end
28
28
  end
29
29
 
@@ -70,7 +70,7 @@ module Chance
70
70
  # Determines the name of the sprite for the given slice. The sprite
71
71
  # by this name may not exist yet.
72
72
  def sprite_name_for_slice(slice, opts)
73
- if slice[:repeat] == "repeat-both"
73
+ if slice[:repeat] == "repeat"
74
74
  return slice[:path] + (opts[:x2] ? "@2x" : "")
75
75
  end
76
76
 
@@ -79,10 +79,23 @@ module Chance
79
79
 
80
80
  # Performs the layout operation, laying either up-to-down, or "
81
81
  # (for repeat-y slices) left-to-right.
82
- def layout_slices_in_sprite(sprite)
82
+ def layout_slices_in_sprite(sprite, opts)
83
83
  # The position is the position in the layout direction. In vertical mode
84
84
  # (the usual) it is the Y position.
85
85
  pos = 0
86
+
87
+ # Adds some padding that will be painted with a pattern so that it is apparent that
88
+ # CSS is wrong.
89
+ # NOTE: though this is only in debug mode, we DO need to make sure it is on a 2px boundary.
90
+ # This makes sure 2x works properly.
91
+ padding = @options[:pad_sprites_for_debugging] ? 2 : 0
92
+
93
+ # The position within a row. It starts at 0 even if we have padding,
94
+ # because we always just add padding when we set the individual x/y pos.
95
+ inset = 0
96
+
97
+ # The length of the row. Length, when layout out vertically (the usual), is the height
98
+ row_length = 0
86
99
 
87
100
  # The size is the current size of the sprite in the non-layout direction;
88
101
  # for example, in the usual, vertical mode, the size is the width.
@@ -94,17 +107,19 @@ module Chance
94
107
  smallest_size = nil
95
108
 
96
109
  is_horizontal = sprite[:use_horizontal_layout]
97
-
98
- sprite[:slices].each do |slice|
110
+
111
+ # Figure out slice width/heights. We cannot rely on slicing to do this for us
112
+ # because some images may be being passed through as-is.
113
+ sprite[:slices].each {|slice|
99
114
  # We must find a canvas either on the slice (if it was actually sliced),
100
115
  # or on the slice's file. Otherwise, we're in big shit.
101
116
  canvas = slice[:canvas] || slice[:file][:canvas]
102
-
117
+
103
118
  # TODO: MAKE A BETTER ERROR.
104
119
  unless canvas
105
- throw "Could not sprite image " + slice[:path] + "; if it is not a PNG"
120
+ throw "Could not sprite image " + slice[:path] + "; if it is not a PNG, make sure you have rmagick installed"
106
121
  end
107
-
122
+
108
123
  # RMagick has a different API than ChunkyPNG; we have to detect
109
124
  # which one we are using, and use the correct API accordingly.
110
125
  if canvas.respond_to?('columns')
@@ -114,10 +129,10 @@ module Chance
114
129
  slice_width = canvas.width
115
130
  slice_height = canvas.height
116
131
  end
117
-
132
+
118
133
  slice_length = is_horizontal ? slice_width : slice_height
119
134
  slice_size = is_horizontal ? slice_height : slice_width
120
-
135
+
121
136
  # When repeating, we must use the least common multiple so that
122
137
  # we can ensure the repeat pattern works even with multiple repeat
123
138
  # sizes. However, we should take into account how much extra we are
@@ -128,8 +143,41 @@ module Chance
128
143
 
129
144
  size = size.lcm slice_size
130
145
  else
131
- size = [size, slice_size].max
146
+ size = [size, slice_size + padding * 2].max
147
+ end
148
+
149
+ slice[:slice_width] = slice_width.to_i
150
+ slice[:slice_height] = slice_height.to_i
151
+ }
152
+
153
+ # Sort slices from widest/tallest (dependent on is_horizontal) or is_vertical
154
+ # NOTE: This means we are technically sorting reversed
155
+ sprite[:slices].sort! {|a, b|
156
+ # WHY <=> NO WORK?
157
+ if is_horizontal
158
+ b[:slice_height] <=> a[:slice_height]
159
+ else
160
+ b[:slice_width] <=> a[:slice_width]
161
+ end
162
+ }
163
+
164
+ sprite[:slices].each do |slice|
165
+ # We must find a canvas either on the slice (if it was actually sliced),
166
+ # or on the slice's file. Otherwise, we're in big shit.
167
+ canvas = slice[:canvas] || slice[:file][:canvas]
168
+
169
+ slice_width = slice[:slice_width]
170
+ slice_height = slice[:slice_height]
171
+
172
+ slice_length = is_horizontal ? slice_width : slice_height
173
+ slice_size = is_horizontal ? slice_height : slice_width
174
+
175
+ if slice[:repeat] != "no-repeat" or inset + slice_size + padding * 2 > size or not @options[:optimize_sprites]
176
+ pos += row_length
177
+ inset = 0
178
+ row_length = 0
132
179
  end
180
+
133
181
 
134
182
  # We have extras for manual tweaking of offsetx/y. We have to make sure there
135
183
  # is padding for this (on either side)
@@ -149,20 +197,44 @@ module Chance
149
197
  elsif slice[:max_offset_y] > 0 and not is_horizontal
150
198
  pos += slice[:max_offset_y]
151
199
  end
152
-
153
-
154
- slice[:sprite_slice_x] = is_horizontal ? pos : 0
155
- slice[:sprite_slice_y] = is_horizontal ? 0 : pos
200
+
201
+ slice[:sprite_slice_x] = (is_horizontal ? pos : inset)
202
+ slice[:sprite_slice_y] = (is_horizontal ? inset : pos)
203
+
204
+ # add padding for x, only if it a) doesn't repeat or b) repeats vertically because it has horizontal layout
205
+ if slice[:repeat] == "no-repeat" or slice[:repeat] == "repeat-y"
206
+ slice[:sprite_slice_x] += padding
207
+ end
208
+
209
+ if slice[:repeat] == "no-repeat" or slice[:repeat] == "repeat-x"
210
+ slice[:sprite_slice_y] += padding
211
+ end
212
+
156
213
  slice[:sprite_slice_width] = slice_width
157
214
  slice[:sprite_slice_height] = slice_height
158
215
 
159
- pos += slice_length
216
+ inset += slice_size + padding * 2
217
+
218
+ # We pad the row length ONLY if it is a repeat-x, repeat-y, or no-repeat image.
219
+ # If it is 'repeat', we do not pad it, because it should be processed raw.
220
+ row_length = [slice_length + (slice[:repeat] != "repeat" ? padding * 2 : 0), row_length].max
221
+
222
+ # In 2X, make sure we are aligned on a 2px grid.
223
+ # We correct this AFTER positioning because we always position on an even grid anyway;
224
+ # we just may leave that even grid if we have an odd-sized image. We do this after positioning
225
+ # so that the next loop knows if there is space.
226
+ if opts[:x2]
227
+ row_length = (row_length.to_f / 2).ceil * 2
228
+ inset = (inset.to_f / 2).ceil * 2
229
+ end
230
+
160
231
  end
232
+ pos += row_length
161
233
 
162
234
  # TODO: USE A CONSTANT FOR THIS WARNING
163
235
  smallest_size = size if smallest_size == nil
164
236
  if size - smallest_size > 10
165
- puts "WARNING: Used more than 10 extra rows or columns to accomdate repeating slices."
237
+ puts "WARNING: Used more than 10 extra rows or columns to accommodate repeating slices."
166
238
  puts "Wasted up to " + (pos * size-smallest_size).to_s + " pixels"
167
239
  end
168
240
 
@@ -177,6 +249,14 @@ module Chance
177
249
  def generate_sprite(sprite)
178
250
  canvas = canvas_for_sprite(sprite)
179
251
  sprite[:canvas] = canvas
252
+
253
+ # If we are padding sprites, we should paint the background something really
254
+ # obvious & obnoxious. Say, magenta. That's obnoxious. A nice light purple wouldn't
255
+ # be bad, but magenta... that will stick out like a sore thumb (I hope)
256
+ if @options[:pad_sprites_for_debugging]
257
+ magenta = ChunkyPNG::Color.rgb(255, 0, 255)
258
+ canvas.rect(0, 0, sprite[:width], sprite[:height], magenta, magenta)
259
+ end
180
260
 
181
261
  sprite[:slices].each do |slice|
182
262
  x = slice[:sprite_slice_x]
@@ -221,14 +301,18 @@ module Chance
221
301
 
222
302
  # Repeat the pattern to fill the width/height.
223
303
  while top < height do
304
+ left = 0
305
+
224
306
  while left < width do
225
- if target.respond_to?(:compose)
226
- target.compose!(source_canvas, left + x, top + y)
307
+ if target.respond_to?(:replace!)
308
+ target.replace!(source_canvas, left + x, top + y)
227
309
  else
228
310
  target.composite!(source_canvas, left + x, top + y)
229
311
  end
312
+
230
313
  left += source_width
231
314
  end
315
+
232
316
  top += source_height
233
317
  end
234
318
 
@@ -295,7 +379,14 @@ module Chance
295
379
 
296
380
  generate_sprite(sprite) if not sprite[:has_generated]
297
381
 
298
- sprite[:canvas].to_blob
382
+ ret = sprite[:canvas].to_blob
383
+
384
+ if Chance.clear_files_immediately
385
+ sprite[:canvas] = nil
386
+ sprite[:has_generated] = false
387
+ end
388
+
389
+ ret
299
390
  end
300
391
 
301
392
  def sprite_names(opts={})
@@ -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