sproutit-sproutcore 1.0.20090721145281 → 1.0.20090721145282

Sign up to get free protection for your applications and to get access to all the features.
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