sproutit-sproutcore 1.0.20090721145281 → 1.0.20090721145282

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 (105) hide show
  1. data/Buildfile +4 -3
  2. data/VERSION.yml +2 -2
  3. data/buildtasks/entry.rake +3 -0
  4. data/buildtasks/manifest.rake +35 -9
  5. data/buildtasks/target.rake +25 -6
  6. data/frameworks/sproutcore/Buildfile +10 -0
  7. data/frameworks/sproutcore/frameworks/datastore/data_sources/data_source.js +41 -20
  8. data/frameworks/sproutcore/frameworks/datastore/data_sources/fixtures.js +14 -43
  9. data/frameworks/sproutcore/frameworks/datastore/models/record.js +11 -0
  10. data/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +6 -3
  11. data/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +5 -1
  12. data/frameworks/sproutcore/frameworks/datastore/system/query.js +10 -7
  13. data/frameworks/sproutcore/frameworks/datastore/system/record_array.js +19 -20
  14. data/frameworks/sproutcore/frameworks/datastore/system/store.js +126 -93
  15. data/frameworks/sproutcore/frameworks/datastore/tests/data_sources/fixtures.js +9 -3
  16. data/frameworks/sproutcore/frameworks/datastore/tests/models/many_attribute.js +6 -1
  17. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/core_methods.js +28 -3
  18. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/destroy.js +13 -5
  19. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/storeDidChangeProperties.js +46 -23
  20. data/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +29 -5
  21. data/frameworks/sproutcore/frameworks/datastore/tests/models/record_attribute.js +13 -4
  22. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/chain.js +109 -0
  23. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/commitChanges.js +69 -15
  24. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/commitChangesFromNestedStore.js +20 -1
  25. data/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/dataHashDidChange.js +4 -1
  26. data/frameworks/sproutcore/frameworks/datastore/tests/system/query/find_all.js +56 -6
  27. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/commitRecord.js +9 -2
  28. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/core_methods.js +45 -2
  29. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/recordDidChange.js +0 -1
  30. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/retrieveRecord.js +53 -6
  31. data/frameworks/sproutcore/frameworks/datastore/tests/system/store/writeDataHash.js +0 -5
  32. data/frameworks/sproutcore/frameworks/desktop/panes/menu.js +47 -27
  33. data/frameworks/sproutcore/frameworks/desktop/system/drag.js +5 -4
  34. data/frameworks/sproutcore/frameworks/desktop/tests/views/collection/mouse.js +23 -12
  35. data/frameworks/sproutcore/frameworks/desktop/tests/views/list/render.js +92 -0
  36. data/frameworks/sproutcore/frameworks/desktop/tests/views/select_field/methods.js +104 -53
  37. data/frameworks/sproutcore/frameworks/desktop/tests/views/select_field/ui.js +2 -0
  38. data/frameworks/sproutcore/frameworks/desktop/views/button.js +4 -3
  39. data/frameworks/sproutcore/frameworks/desktop/views/collection.js +6 -2
  40. data/frameworks/sproutcore/frameworks/desktop/views/list.js +9 -0
  41. data/frameworks/sproutcore/frameworks/desktop/views/list_item.js +2 -2
  42. data/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +3 -3
  43. data/frameworks/sproutcore/frameworks/desktop/views/popup_button.js +9 -1
  44. data/frameworks/sproutcore/frameworks/desktop/views/select_field.js +80 -102
  45. data/frameworks/sproutcore/frameworks/foundation/controllers/array.js +0 -1
  46. data/frameworks/sproutcore/frameworks/foundation/english.lproj/text_field.css +5 -1
  47. data/frameworks/sproutcore/frameworks/foundation/panes/pane.js +8 -1
  48. data/frameworks/sproutcore/frameworks/foundation/private/tree_item_observer.js +0 -1
  49. data/frameworks/sproutcore/frameworks/foundation/system/datetime.js +31 -3
  50. data/frameworks/sproutcore/frameworks/foundation/system/event.js +0 -4
  51. data/frameworks/sproutcore/frameworks/foundation/system/render_context.js +3 -7
  52. data/frameworks/sproutcore/frameworks/foundation/system/request.js +3 -4
  53. data/frameworks/sproutcore/frameworks/foundation/system/user_defaults.js +78 -17
  54. data/frameworks/sproutcore/frameworks/foundation/system/utils.js +9 -0
  55. data/frameworks/sproutcore/frameworks/foundation/tests/controllers/array/single_case.js +2 -2
  56. data/frameworks/sproutcore/frameworks/foundation/tests/system/core_query/jquery_selector.js +2 -2
  57. data/frameworks/sproutcore/frameworks/foundation/tests/system/datetime.js +5 -0
  58. data/frameworks/sproutcore/frameworks/foundation/tests/system/request.js +2 -2
  59. data/frameworks/sproutcore/frameworks/foundation/tests/system/user_defaults.js +1 -4
  60. data/frameworks/sproutcore/frameworks/foundation/tests/validators/validator.js +20 -0
  61. data/frameworks/sproutcore/frameworks/foundation/tests/views/image/ui.js +13 -0
  62. data/frameworks/sproutcore/frameworks/foundation/tests/views/text_field/ui.js +132 -0
  63. data/frameworks/sproutcore/frameworks/foundation/tests/views/view/isVisibleInWindow.js +6 -3
  64. data/frameworks/sproutcore/frameworks/foundation/validators/validator.js +8 -5
  65. data/frameworks/sproutcore/frameworks/foundation/views/image.js +18 -5
  66. data/frameworks/sproutcore/frameworks/foundation/views/text_field.js +292 -21
  67. data/frameworks/sproutcore/frameworks/foundation/views/view.js +13 -14
  68. data/frameworks/sproutcore/frameworks/mini/license.js +28 -0
  69. data/frameworks/sproutcore/frameworks/runtime/core.js +35 -0
  70. data/frameworks/sproutcore/frameworks/runtime/mixins/enumerable.js +1 -1
  71. data/frameworks/sproutcore/frameworks/runtime/system/sparse_array.js +79 -5
  72. data/frameworks/sproutcore/frameworks/runtime/tests/mixins/enumerable.js +6 -6
  73. data/frameworks/sproutcore/frameworks/runtime/tests/system/sparse_array.js +53 -0
  74. data/frameworks/sproutcore/frameworks/testing/system/plan.js +4 -0
  75. data/frameworks/sproutcore/frameworks/testing/system/runner.js +1 -1
  76. data/frameworks/sproutcore/themes/standard_theme/english.lproj/radio.css +4 -0
  77. data/gen/design/Buildfile +23 -0
  78. data/gen/design/README +1 -0
  79. data/gen/design/USAGE +10 -0
  80. data/gen/design/templates/english.lproj/@filename@.js +16 -0
  81. data/gen/page/Buildfile +36 -0
  82. data/gen/page/README +1 -0
  83. data/gen/page/USAGE +15 -0
  84. data/gen/page/templates/pages/@target_name@/Buildfile +16 -0
  85. data/gen/page/templates/pages/@target_name@/core.js +22 -0
  86. data/gen/page/templates/pages/@target_name@/english.lproj/body.css +1 -0
  87. data/gen/page/templates/pages/@target_name@/english.lproj/body.rhtml +7 -0
  88. data/gen/page/templates/pages/@target_name@/english.lproj/strings.js +15 -0
  89. data/gen/view/README +1 -1
  90. data/gen/view/USAGE +5 -5
  91. data/lib/sproutcore/builders/base.rb +13 -2
  92. data/lib/sproutcore/builders/html.rb +28 -1
  93. data/lib/sproutcore/builders/minify.rb +84 -18
  94. data/lib/sproutcore/builders/test.rb +2 -1
  95. data/lib/sproutcore/helpers/entry_sorter.rb +16 -1
  96. data/lib/sproutcore/helpers/static_helper.rb +32 -4
  97. data/lib/sproutcore/helpers/tag_helper.rb +65 -0
  98. data/lib/sproutcore/models/manifest.rb +40 -6
  99. data/lib/sproutcore/models/target.rb +12 -3
  100. data/lib/sproutcore/rack/builder.rb +56 -4
  101. data/lib/sproutcore/tools/manifest.rb +1 -0
  102. data/lib/sproutcore/tools/server.rb +1 -0
  103. data/lib/sproutcore/tools.rb +21 -1
  104. data/lib/sproutcore.rb +13 -0
  105. metadata +16 -1
@@ -130,7 +130,14 @@ module SC
130
130
  # self
131
131
  #
132
132
  def compile(render_engine, input_path, content_for_key = nil)
133
- content_for_key = self.default_content_for_key if content_for_key.nil?
133
+
134
+ if content_for_key.nil?
135
+ if @in_partial
136
+ content_for_key = :_partial_
137
+ else
138
+ content_for_key = self.default_content_for_key
139
+ end
140
+ end
134
141
 
135
142
  if !File.exist?(input_path)
136
143
  raise "html_builder could compile file at #{input_path} because the file could not be found"
@@ -140,6 +147,7 @@ module SC
140
147
  @renderer = render_engine # save for capture...
141
148
 
142
149
  input = File.read(input_path)
150
+
143
151
  content_for content_for_key do
144
152
  _render_compiled_template( render_engine.compile(input) )
145
153
  end
@@ -150,6 +158,25 @@ module SC
150
158
 
151
159
  private
152
160
 
161
+ # Renders an entry as a partial. This will insert the results inline
162
+ # instead of into a content div.
163
+ def render_partial(entry)
164
+
165
+ # save off
166
+ old_partial = @content_for__partial_
167
+ old_in_partial = @in_partial
168
+
169
+ @content_for__partial_ = ''
170
+ @in_partial = true
171
+ render_entry(entry)
172
+ ret = @content_for__partial_
173
+ @content_for__partial_ = old_partial
174
+ @in_partial = old_in_partial
175
+
176
+ return ret
177
+
178
+ end
179
+
153
180
  # Renders a single entry. The entry will be staged and then its
154
181
  # render task will be executed.
155
182
  def render_entry(entry)
@@ -33,35 +33,101 @@ module SC
33
33
  end
34
34
 
35
35
  def build_css(dst_path)
36
- yui_root = File.expand_path(File.join(LIBPATH, '..', 'vendor', 'yui-compressor'))
37
- jar_path = File.join(yui_root, 'yuicompressor-2.4.2.jar')
38
- FileUtils.mkdir_p(File.dirname(dst_path)) # make sure loc exists...
39
- filecompress = "java -jar " + jar_path + " --charset utf-8 --line-break 0 --nomunge --preserve-semi --disable-optimizations " + entry.source_path + " -o " + dst_path
40
- SC.logger.info 'Compressing CSS with YUI .... '+ dst_path
41
- SC.logger.debug `#{filecompress}`
36
+ yui_root = File.expand_path(File.join(LIBPATH, '..', 'vendor', 'yui-compressor'))
37
+ jar_path = File.join(yui_root, 'yuicompressor-2.4.2.jar')
38
+ FileUtils.mkdir_p(File.dirname(dst_path)) # make sure loc exists...
39
+ filecompress = "java -jar " + jar_path + " --charset utf-8 --line-break 0 --nomunge --preserve-semi --disable-optimizations " + entry.source_path + " -o \"" + dst_path + "\" 2>&1"
40
+ SC.logger.info 'Compressing CSS with YUI .... '+ dst_path
41
+ SC.logger.debug `#{filecompress}`
42
42
 
43
- if $?.exitstatus != 0
44
- SC.logger.fatal("!!!!YUI compressor failed, please check that your css code is valid.")
45
- SC.logger.fatal("!!!!Failed compressing CSS... "+ dst_path)
46
- end
47
- end
43
+ if $?.exitstatus != 0
44
+ _report_error(output, entry.filename, entry.source_path)
45
+ SC.logger.fatal("!!!!YUI compressor failed, please check that your css code is valid.")
46
+ SC.logger.fatal("!!!!Failed compressing CSS... "+ dst_path)
47
+ end
48
+ end
48
49
 
49
50
  # Minify some javascript by invoking the YUI compressor.
50
51
  def build_javascript(dst_path)
51
52
  yui_root = File.expand_path(File.join(LIBPATH, '..', 'vendor', 'yui-compressor'))
52
53
  jar_path = File.join(yui_root, 'yuicompressor-2.4.2.jar')
53
54
  FileUtils.mkdir_p(File.dirname(dst_path)) # make sure loc exists...
54
- filecompress = "java -jar " + jar_path + " --charset utf-8 --line-break 80 " + entry.source_path + " -o " + dst_path
55
- SC.logger.info 'Compressing with YUI .... '+ dst_path
56
- SC.logger.debug `#{filecompress}`
57
-
55
+ filecompress = "java -jar " + jar_path + " --charset utf-8 --line-break 80 " + entry.source_path + " -o \"" + dst_path + "\" 2>&1"
56
+ SC.logger.info 'Compressing with YUI: '+ dst_path + "..."
57
+
58
+ output = `#{filecompress}` # It'd be nice to just read STDERR, but
59
+ # I can't find a reasonable, commonly-
60
+ # installed, works-on-all-OSes solution.
58
61
  if $?.exitstatus != 0
59
- SC.logger.fatal("!!!!YUI compressor failed, please check that your js code is valid and doesn't contain reserved statements like debugger;")
62
+ _report_error(output, entry.filename, entry.source_path)
63
+ SC.logger.fatal("!!!!YUI compressor failed, please check that your js code is valid")
60
64
  SC.logger.fatal("!!!!Failed compressing ... "+ dst_path)
61
65
  end
62
66
 
63
67
  end
64
-
65
- end
66
68
 
69
+
70
+ def _report_error(output, input_filename, input_filepath)
71
+ # The output might have some clues to what exactly was wrong, and it'll
72
+ # be convenient for users if we include the subset. So we'll read the
73
+ # line numbers from any output lines that start with "[ERROR]" those
74
+ # lines, too.
75
+ if output
76
+ parsed_a_line = false
77
+ output.each_line { |output_line|
78
+ output_line = output_line.chomp
79
+ if (output_line =~ /^\[ERROR\] (\d+):(\d+):.*/) != nil
80
+ line_number = $1
81
+ position = $2
82
+ parsed_a_line = true
83
+ if ( position && position.to_i > 0 )
84
+ # Read just that line and output it.
85
+ # (sed -n '3{;p;q;}' would probably be faster, but not
86
+ # universally available)
87
+ line_number = line_number.to_i
88
+ line_counter = 1
89
+ begin
90
+ file = File.new(input_filepath, "r")
91
+ outputted_line = false
92
+ previous_line = nil
93
+ while (file_line = file.gets)
94
+
95
+ if ( line_counter == line_number )
96
+ message = "YUI compressor error: #{output_line} on line #{line_number} of #{input_filename}:\n"
97
+ if !previous_line.nil?
98
+ message += " [#{line_number - 1}] #{previous_line}"
99
+ end
100
+ message += " --> [#{line_number}] #{file_line}"
101
+
102
+ SC.logger.error message
103
+ outputted_line = true
104
+ break
105
+ else
106
+ previous_line = file_line
107
+ end
108
+ line_counter += 1
109
+ end
110
+ file.close
111
+ rescue => err
112
+ SC.logger.error "Could not read the actual line from the file: #{err}"
113
+ end
114
+
115
+ if !outputted_line
116
+ SC.logger.error "YUI compressor error: #{output_line}, but couldn't read that line in the input file"
117
+ end
118
+ end
119
+ end
120
+ }
121
+
122
+ # If we didn't handle at least one line of output specially, then
123
+ # just output it all.
124
+ if !parsed_a_line
125
+ SC.logger.error output
126
+ end
127
+ end
128
+ end
129
+
130
+
131
+ end
132
+
67
133
  end
@@ -48,7 +48,8 @@ module SC
48
48
  # conflict with any others.
49
49
  def render_jstest(entry)
50
50
  lines = readlines(entry.staging_path)
51
- lines.unshift %[<script type="text/javascript">\nif (typeof SC !== "undefined") SC.mode = "TEST_MODE";\n(function() {\n]
51
+ pathname = entry.staging_path.gsub(/^.+\/staging\//,'').gsub(/"/, '\"')
52
+ lines.unshift %[<script type="text/javascript">\nif (typeof SC !== "undefined") {\n SC.mode = "TEST_MODE";\n SC.filename = "#{pathname}"; \n}\n(function() {\n]
52
53
  lines.push %[\n})();\n</script>\n]
53
54
  @content_for_final = (@content_for_final || '') + lines.join("")
54
55
  end
@@ -29,7 +29,22 @@ module SC
29
29
  def sort(entries)
30
30
  # first sort entries by filename - ignoring case
31
31
  entries = entries.sort do |a,b|
32
- (a.filename || '').to_s.downcase <=> (b.filename || '').to_s.downcase
32
+ a = (a.filename || '').to_s.downcase
33
+ b = (b.filename || '').to_s.downcase
34
+
35
+ # lproj/foo_page.js and main.js are loaded last
36
+ a_kind = (a =~ /lproj\/.+_page\.js$/) ? 1 : -1
37
+ a_kind = 2 if a =~ /main.js$/
38
+
39
+ b_kind = (b =~ /lproj\/.+_page\.js$/) ? 1 : -1
40
+ b_kind = 2 if b =~ /main.js$/
41
+
42
+ if a_kind != b_kind
43
+ a_kind <=> b_kind
44
+ else
45
+ a <=> b
46
+ end
47
+
33
48
  end
34
49
  all_entries = entries.dup # needed for sort...
35
50
 
@@ -94,7 +94,7 @@ module SC
94
94
 
95
95
  # include either the entry URL or URL of ordered entries
96
96
  # depending on setup
97
- if combine_javascript
97
+ if cur_target.config.combine_javascript
98
98
  urls << cur_entry.cacheable_url
99
99
  else
100
100
  urls += cur_entry.ordered_entries.map { |e| e.cacheable_url }
@@ -115,9 +115,27 @@ module SC
115
115
  urls.join("\n")
116
116
  end
117
117
 
118
+ # Attempts to render the named entry as a partial
119
+ #
120
+ # === Options
121
+ # language:: the language to use. defaults to current
122
+ #
123
+ def partial(resource_name, opts = {})
124
+ resource_name = resource_name.to_s
125
+ m = self.manifest
126
+ if opts[:language]
127
+ m = target.manifest_for(:language => opts[:language]).build!
128
+ end
129
+
130
+ entry = m.find_entry(resource_name, :hidden => true, :entry_type => :html)
131
+ return entry.nil? ? '' : render_partial(entry)
132
+ end
133
+
118
134
  # Returns the URL for the named resource
119
135
  def sc_static(resource_name, opts = {})
120
136
 
137
+ resource_name = resource_name.to_s
138
+
121
139
  # determine which manifest to search. if a language is explicitly
122
140
  # specified, lookup manifest for that language. otherwise use
123
141
  # current manifest.
@@ -127,10 +145,19 @@ module SC
127
145
  end
128
146
 
129
147
  entry = m.find_entry(resource_name)
130
- entry.nil? ? '' : entry.cacheable_url
148
+ return '' if entry.nil?
149
+ return entry.friendly_url if opts[:friendly] && entry.friendly_url
150
+ return entry.cacheable_url
131
151
  end
132
152
  alias_method :static_url, :sc_static
133
153
 
154
+ # Returns the URL of the named target's index.html, if it has one
155
+ def sc_target(resource_name, opts = {})
156
+ opts[:friendly] = true
157
+ resource_name = "#{resource_name}:index.html"
158
+ sc_static(resource_name, opts)
159
+ end
160
+
134
161
  # Allows you to specify HTML resource this html template should be
135
162
  # merged into. Optionally also specify the layout file to use when
136
163
  # building this resource.
@@ -168,8 +195,9 @@ module SC
168
195
  return ret
169
196
  end
170
197
 
171
- def title
172
- target.config.title || target.target_name.to_s.sub(/^\//,'').gsub(/[-_\/]/,' ').split(' ').map { |x| x.capitalize }.join(' ')
198
+ def title(cur_target=nil)
199
+ cur_target = self.target if cur_target.nil?
200
+ cur_target.config.title || cur_target.target_name.to_s.sub(/^\//,'').gsub(/[-_\/]/,' ').split(' ').map { |x| x.capitalize }.join(' ')
173
201
  end
174
202
 
175
203
  private
@@ -84,6 +84,71 @@ module SC
84
84
  fix_double_escape(html_escape(html.to_s))
85
85
  end
86
86
 
87
+ # Simple link_to can wrap a passed string with a link to a specified
88
+ # target or static asset. If you pass a block then the block will be
89
+ # invoked and its resulting content linked. You can also pass
90
+ # :popup, :title, :id, :class, and :style
91
+ def link_to(content, opts=nil, &block)
92
+ if block_given?
93
+ concat(link_to(capture(&block), content), block.binding);
94
+ return ''
95
+ end
96
+
97
+ if !content.instance_of?(String) && opts.nil?
98
+ opts = content
99
+ content = nil
100
+ end
101
+
102
+ opts = { :href => opts } if opts.instance_of? String
103
+ opts = HashStruct.new(opts)
104
+ html_attrs = HashStruct.new
105
+ is_target = false
106
+
107
+ if opts.href
108
+ html_attrs.href = opts.href
109
+ elsif opts.target
110
+
111
+ is_target = (target.target_name.to_sym == "/#{opts.target.to_s}".to_sym)
112
+
113
+ # supply title if needed
114
+ if content.nil?
115
+ cur_target = is_target ? target : target.target_for(opts.target)
116
+ content = title(cur_target) if cur_target
117
+ content = opts.target if content.nil?
118
+ end
119
+
120
+ # if current==false, then don't link if current target matches
121
+ if !opts.current.nil? && (opts.current==false) && is_target
122
+ return %(<span class="anchor current">#{content}</span>)
123
+ end
124
+
125
+ html_attrs.href = sc_target(opts.target, :language => opts.language)
126
+ elsif opts.static
127
+ html_attrs.href = sc_static(opts.static, :language => opts.language)
128
+ end
129
+
130
+ if opts.popup
131
+ popup = opts.popup
132
+ html_attrs.target = popup.instance_of?(String) ? popup : '_blank'
133
+ end
134
+
135
+ %w[title id class style].each do |key|
136
+ html_attrs[key] = opts[key] if opts[key]
137
+ end
138
+
139
+ # add "current" class name
140
+ if is_target
141
+ html_attrs[:class] = [html_attrs[:class], 'current'].compact.join(' ')
142
+ end
143
+
144
+ ret = ["<a "]
145
+ html_attrs.each { |k,v| ret << [k,'=','"',v,'" '].join('') }
146
+ ret << '>'
147
+ ret << content
148
+ ret << '</a>'
149
+ return ret.join('')
150
+ end
151
+
87
152
  private
88
153
  def content_tag_string(name, content, options)
89
154
  tag_options = options ? tag_options(options) : ""
@@ -88,6 +88,8 @@ module SC
88
88
  return self
89
89
  end
90
90
 
91
+ def built?; @is_built; end
92
+
91
93
  # Resets the manifest entries. this is called before a build is
92
94
  # performed. This will reset only the entries, none of the other props.
93
95
  #
@@ -213,6 +215,10 @@ module SC
213
215
  end
214
216
  opts.staging_path ||= unique_staging_path(staging_path)
215
217
 
218
+ # generate a unique cache path from the staging page. just sub the
219
+ # staging root for the cache root
220
+ opts.cache_path ||= unique_cache_path(entry.cache_path)
221
+
216
222
  # copy other useful entries
217
223
  opts.source_entry = entry
218
224
  opts.source_entries = [entry]
@@ -289,11 +295,28 @@ module SC
289
295
  #
290
296
  def find_entry(fragment, opts = {}, seen=nil)
291
297
 
298
+ entry_extname = entry_rootname = ret = target_name = nil
299
+
300
+ # optionally you can specify an explicit target name
301
+ split_index = fragment.index(':') # find first index
302
+ unless split_index.nil?
303
+ target_name = '/' + fragment[0..(split_index-1)] if split_index>0
304
+ fragment = fragment[(split_index+1)..-1] # remove colon
305
+ end
306
+
307
+ # find the current manifest
308
+ if target_name
309
+ cur_target = self.target.target_for(target_name) || self.target
310
+ cur_manifest = cur_target.manifest_for(self.variation).build!
311
+ else
312
+ cur_manifest = self
313
+ end
314
+
292
315
  extname = File.extname(fragment)
293
316
  rootname = fragment.sub(/#{extname}$/, '')
294
- entry_extname = entry_rootname = nil
295
317
 
296
- ret = entries(:hidden => opts[:hidden]).reject do |entry|
318
+ # look on our own target only if target is named
319
+ ret = cur_manifest.entries(:hidden => opts[:hidden]).reject do |entry|
297
320
  if entry.has_options?(opts)
298
321
  entry_extname = File.extname(entry.filename)
299
322
  entry_rootname = entry.filename.sub(/#{entry_extname}$/,'')
@@ -301,16 +324,17 @@ module SC
301
324
  else
302
325
  ext_match = false
303
326
  end
327
+
304
328
  !(ext_match && (/#{rootname}$/ =~ entry_rootname))
305
329
  end
306
-
330
+
307
331
  ret = ret.first
308
-
332
+
309
333
  # if no match was found, search the same manifests in required targets
310
334
  if ret.nil?
311
335
  seen = Set.new if seen.nil?
312
- seen << self.target
313
- self.target.expand_required_targets.each do |t|
336
+ seen << cur_manifest.target
337
+ cur_manifest.target.expand_required_targets.each do |t|
314
338
  next if seen.include?(t) # avoid recursion
315
339
 
316
340
  manifest = t.manifest_for(self.variation).build!
@@ -330,6 +354,16 @@ module SC
330
354
  end
331
355
  return path
332
356
  end
357
+
358
+ # Finds a unique cache path starting with the root proposed staging
359
+ # path.
360
+ def unique_cache_path(path)
361
+ paths = entries(:hidden => true).map { |e| e.cache_path }
362
+ while paths.include?(path)
363
+ path = path.sub(/(__\$[0-9]+)?(\.\w+)?$/,"__$#{next_staging_uuid}\\2")
364
+ end
365
+ return path
366
+ end
333
367
 
334
368
  protected
335
369
 
@@ -241,7 +241,10 @@ module SC
241
241
  # === Returns
242
242
  # A build number string
243
243
  #
244
- def compute_build_number(seen=nil)
244
+ def compute_build_number(seen=nil, opts = {})
245
+
246
+ # reset cache if forced to recompute
247
+ build_number = nil if opts[:force]
245
248
 
246
249
  # Look for a global build_numbers hash and try that
247
250
  if (build_numbers = config.build_numbers)
@@ -272,10 +275,16 @@ module SC
272
275
  # causing infinite loops. Normally this should not be necessary, but
273
276
  # we put this here to gaurd against misconfigured projects
274
277
  seen ||= []
275
- required_targets.each do |ct|
278
+
279
+ _targets = required_targets(:theme => true).sort do |a,b|
280
+ (a.target_name||'').to_s <=> (b.target_name||'').to_s
281
+ end
282
+
283
+ _targets.each do |ct|
276
284
  next if seen.include?(ct)
285
+ ct.prepare!
277
286
  seen << ct
278
- digests << (ct.build_number || ct.compute_build_number(seen))
287
+ digests << ct.compute_build_number(seen, :force => true)
279
288
  end
280
289
 
281
290
  # Finally digest the complete string - tada! build number
@@ -171,18 +171,70 @@ module SC
171
171
  # Reloads the project if reloading is enabled. At maximum this will
172
172
  # reload the project every 5 seconds.
173
173
  def reload_project!
174
+
175
+ monitor_project!
176
+
174
177
  # don't reload if no project or is disabled
175
178
  return if @project.nil? || !@project.config.reload_project
176
179
 
177
- # reload after a delay of 5 sec
178
- reload_delay = (Time.now - @last_reload_time)
179
-
180
- if reload_delay > 5
180
+ if @project_did_change
181
+ @project_did_change = false
181
182
  SC.logger.info "Rebuilding project manifest"
182
183
  @project.reload!
183
184
  end
184
185
  end
186
+
187
+ def monitor_project!
188
+ if !@should_monitor
189
+ @should_monitor = true
190
+ @project_root = @project.project_root
191
+
192
+ # collect initial info on project
193
+ files = Dir.glob(@project_root / '**' / '*')
194
+ # follow 1-level of symlinks
195
+ files += Dir.glob(@project_root / '**' / '*' / '**' / '*')
196
+ tmp_path = /^#{Regexp.escape(@project_root / 'tmp')}/
197
+ files.reject! { |f| f =~ tmp_path }
198
+ files.reject! { |f| File.directory?(f) }
199
+
200
+ @project_file_count = files.size
201
+ @project_mtime = files.map { |x| File.mtime(x).to_i }.max
202
+
203
+ Thread.new do
204
+ while @should_monitor
205
+
206
+ # only need to start scanning again 2 seconds after the last
207
+ # request was serviced.
208
+ reload_delay = (Time.now - @last_reload_time)
209
+ if reload_delay > 2
210
+ files = Dir.glob(@project_root / '**' / '*')
211
+ # follow 1-level of symlinks
212
+ files += Dir.glob(@project_root / '**' / '*' / '**' / '*')
213
+ tmp_path = /^#{Regexp.escape(@project_root / 'tmp')}/
214
+ files.reject! { |f| f =~ tmp_path }
215
+ files.reject! { |f| File.directory?(f) }
216
+
217
+ cur_file_count = files.size
218
+ cur_mtime = files.map { |x| File.mtime(x).to_i }.max
219
+
220
+ if (@project_file_count != cur_file_count) || (@project_mtime != cur_mtime)
221
+ SC.logger.info "Detected project change. Will rebuild manifest"
222
+ @project_did_change = true
223
+ @project_file_count = cur_file_count
224
+ @project_mtime = cur_mtime
225
+ end
226
+ end
227
+
228
+ sleep(5)
229
+ end
230
+ end
231
+ end
232
+ end
185
233
 
234
+ def stop_monitor!
235
+ @should_monitor = false
236
+ end
237
+
186
238
  def target_for(url)
187
239
 
188
240
  # get targets
@@ -16,6 +16,7 @@ module SC
16
16
  :format => :optional,
17
17
  :output => :output,
18
18
  :all => false,
19
+ ['--build-numbers', '-B'] => :optional,
19
20
  ['--include-required', '-r'] => false }
20
21
 
21
22
  desc "manifest [TARGET..]", "Generates a manifest for the specified targets"
@@ -39,6 +39,7 @@ module SC
39
39
  ARGV.clear # do not pass onto IRB
40
40
  IRB.start
41
41
  else
42
+ SC.logger << "SproutCore v#{SC::VERSION} Development Server\n"
42
43
  SC::Rack::Service.start(options.merge(:project => project))
43
44
  end
44
45
  end
@@ -253,15 +253,35 @@ module SC
253
253
  if (languages = options.languages).nil?
254
254
  languages = targets.map { |t| t.installed_languages }
255
255
  else
256
- languages = languages.split(':').map { |l| l.to_sym }
256
+ languages = languages.split(',').map { |l| l.to_sym }
257
257
  end
258
258
  languages.flatten.uniq.compact
259
259
  end
260
260
 
261
+ # Discovers build numbers requested for the build and sets them in the
262
+ # in the env if needed.
263
+ def find_build_numbers(*targets)
264
+ if options['build-numbers']
265
+ numbers = {}
266
+ options['build-numbers'].split(',').each do |pair|
267
+ pair = pair.split(':')
268
+ if pair.length < 2
269
+ fatal! "Could not parse build numbers! #{options['build-numbers']}"
270
+ end
271
+ numbers["/#{pair[0]}"] = pair[1]
272
+ end
273
+ SC.env.build_numbers = numbers
274
+ SC.logger.info "Using build numbers: #{numbers.map { |k,v| "#{k}: #{v}" }.join(',')}"
275
+ end
276
+ end
277
+
261
278
  # Core method to process command line options and then build a manifest.
262
279
  # Shared by sc-manifest, sc-build and sc-docs commands.
263
280
  def build_manifests(*targets)
264
281
 
282
+ # setup build numbers
283
+ find_build_numbers(*targets)
284
+
265
285
  requires_project! # get project
266
286
  targets = find_targets(*targets) # get targets
267
287
  languages = find_languages(*targets) # get languages
data/lib/sproutcore.rb CHANGED
@@ -125,6 +125,19 @@ module SproutCore
125
125
  def self.project; @project; end
126
126
  def self.project=(project); @project = project; end
127
127
 
128
+ # Attempts to load a project for the current working directory or from the
129
+ # passed directory location. Returns nil if no project could be detected.
130
+ # This is just a shorthand for creating a Project object. It is useful
131
+ # when using the build tools as a Ruby library
132
+ def self.load_project(path = nil, opts = {})
133
+ path = File.expand_path(path.nil? ? Dir.pwd : path)
134
+ if FalseClass === opts[:discover]
135
+ SC::Project.load path, :parent => SC.builtin_project
136
+ else # attempt to autodiscover unless disabled
137
+ SC::Project.load_nearest_project path, :parent => SC.builtin_project
138
+ end
139
+ end
140
+
128
141
  end # module SC
129
142
 
130
143
  SC = SproutCore # alias