sproutcore 0.9.20 → 0.9.21

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,15 @@
1
+ == 0.9.21
2
+
3
+ * Disabled platform name in URLs. We will be using a different approach in the next version of the build tools, so this will avoid incompatible URLs from being published. You can still technically build with platforms, but that should be avoided.
4
+
5
+ * Added support for using build numbers in URL strings instead of query string parameters. This will make caches flush more reliably.
6
+
7
+ * Added support for building multiple platforms. This is to be used with mobile platforms.
8
+
9
+ * Unit tests can now be simple .js files. These files will be inserted into an html template automatically.
10
+
11
+ == 0.9.20
12
+
1
13
  == 0.9.19
2
14
 
3
15
  * back out relative-path change which breaks dev mode [Erich Ocean]
data/Manifest.txt CHANGED
@@ -339,6 +339,7 @@ jsdoc/test/variable_redefine.js
339
339
  jsdoc/test.js
340
340
  lib/sproutcore/build_tools/html_builder.rb
341
341
  lib/sproutcore/build_tools/resource_builder.rb
342
+ lib/sproutcore/build_tools/test_template.rhtml
342
343
  lib/sproutcore/build_tools.rb
343
344
  lib/sproutcore/bundle.rb
344
345
  lib/sproutcore/bundle_installer.rb
@@ -410,4 +411,5 @@ spec/sproutcore_spec.rb
410
411
  tasks/deployment.rake
411
412
  tasks/environment.rake
412
413
  tasks/rspec.rake
413
- tasks/website.rake
414
+ tasks/website.rake
415
+ yui_compressor/yuicompressor-2.4.2.jar
data/bin/sc-build CHANGED
@@ -47,6 +47,11 @@ languages = nil
47
47
  clean = false
48
48
  docs = false
49
49
  build_all = false
50
+ build_numbers = nil
51
+ platforms = nil
52
+ symlink = false
53
+
54
+ $0 = "sc-build #{ARGV * ' '}"
50
55
 
51
56
  opts = OptionParser.new do |opts|
52
57
  opts.version = SproutCore::VERSION::STRING
@@ -76,7 +81,22 @@ opts = OptionParser.new do |opts|
76
81
  opts.on("-v", "--[no-]verbose", "If set, extra debug output will be displayed during the build") do |opt_verbose|
77
82
  verbose = !!opt_verbose
78
83
  end
79
-
84
+
85
+ opts.on("-p", "--platform=PLATFORM", "A comma separated list of the platforms you want to build. If you do not pass this option, the build tool will build all platforms found in the target bundle.") do |opt_platform|
86
+ platforms = opt_platform.split(',').map { |x| x.to_sym }
87
+ end
88
+
89
+ opts.on("-b", "--build=BUILD_NUMBERS", "build numbers. Name build number for taret or list of build numbers if needed") do |opt_build|
90
+ build_numbers = {}
91
+ opt_build = opt_build.split(',').map { |x| x.split(':') }.each do |x|
92
+ if x.size == 1
93
+ build_numbers[bundle_name.to_sym] = x[0].to_sym
94
+ elsif x.size > 1
95
+ build_numbers[x[0].to_sym] = x[1].to_sym
96
+ end
97
+ end
98
+ end
99
+
80
100
  opts.on("-L", "--languages=LANGUAGE", "Pass a comma separated list of the specific languages you want to build. If you do not pass this option, the build tool will build all languages required by the bundles to function") do |opt_lang|
81
101
  languages = opt_lang.split(',').map { |x| x.to_sym }
82
102
  end
@@ -93,6 +113,10 @@ opts = OptionParser.new do |opts|
93
113
  build_all = !!opt_all
94
114
  end
95
115
 
116
+ opts.on('-a', '--[no-]symlink', "Use symlinks where possible to reduce build size. Only use this option if you are both building and deploying on machines that support symlinks") do |opt_symlink|
117
+ symlink = !!opt_symlink
118
+ end
119
+
96
120
  end
97
121
  opts.parse!
98
122
 
@@ -105,7 +129,7 @@ SC.logger.level = (verbose) ? Logger::DEBUG : Logger::INFO
105
129
  SC.logger.progname = $0
106
130
 
107
131
  # Load Library
108
- library = SC.library_for(library_root, :public_root => build_root, :build_mode => build_mode)
132
+ library = SC.library_for(library_root, :public_root => build_root, :build_mode => build_mode, :build_numbers => build_numbers)
109
133
 
110
134
  if library.nil?
111
135
  SC.logger.fatal("No SproutCore library could be found. Make sure you are inside of your project directory (the one with an sc-config file in it).")
@@ -116,13 +140,14 @@ end
116
140
  ## FIND BUNDLES TO BUILD
117
141
  ##
118
142
 
119
- puts "build_root = #{build_root}"
143
+ target_bundle = [] # save for platform detection, does not include required...
120
144
 
121
145
  # Find the bundles to build.
122
146
  if bundle_name
123
147
  # Get bundle
124
148
  bundle = library.bundle_for(bundle_name.to_sym)
125
149
  not_found(bundle) if bundle.nil?
150
+ target_bundles = [bundle]
126
151
 
127
152
  # If dependencies are required, get those bundles as well
128
153
  if include_dependencies
@@ -137,6 +162,7 @@ if bundle_name
137
162
  else
138
163
  bundles = (build_all) ? library.bundles : library.default_bundles_for_build
139
164
  SC.logger.info("Building ALL bundles:\n #{bundles.map { |x| x.bundle_name } * ', '}...")
165
+ target_bundles = bundles
140
166
  end
141
167
 
142
168
  ############################################################
@@ -151,7 +177,16 @@ languages ||= library.environment_for(nil)[:build_languages]
151
177
  languages = nil unless languages.nil? || languages.size > 0
152
178
  languages ||= bundles.map { |b| b.installed_languages }.flatten.uniq
153
179
 
180
+ # Detect required platforms to build
181
+ # Unless platforms are specified on the command line, we build any platforms
182
+ # specified in the build_platforms config or text languages in the target
183
+ # bundles.
184
+ platforms ||= library.environment_for(nil)[:build_platforms]
185
+ platforms = nil unless platforms.nil? || platforms.size > 0
186
+ platforms ||= target_bundles.map { |b| b.installed_platforms }.flatten.uniq
187
+
154
188
  SC.logger.info("Building Languages: #{languages.join(', ')}")
189
+ SC.logger.info("Building Platforms: #{platforms.join(', ')}")
155
190
 
156
191
  SC.logger.info('')
157
192
  bundles.each do |bundle|
@@ -162,7 +197,9 @@ bundles.each do |bundle|
162
197
  FileUtils.rm_rf(bundle.build_root)
163
198
  end
164
199
 
165
- bundle.build(*languages)
200
+ SC.logger.info("~ Build Number: #{bundle.build_number}")
201
+ $0 = "sc-build: building #{bundle.bundle_name}"
202
+ platforms.each { |p| bundle.build(p, *languages) }
166
203
 
167
204
  if docs
168
205
  SC.logger.debug('~ Building docs')
data/config/hoe.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'sproutcore/version'
2
2
 
3
3
  AUTHOR = "Charles Jolley, Erich Ocean" # can also be an array of Authors
4
- EMAIL = 'charles@sproutit.com, erich@atlasocean.com'
4
+ EMAIL = 'charles@sproutit.com'
5
5
  DESCRIPTION = "SproutCore - JavaScript Application Framework + Build Tools"
6
6
  GEM_NAME = 'sproutcore' # what ppl will type to install your gem
7
7
  RUBYFORGE_PROJECT = 'sproutcore' # The unix name for your project
@@ -19,11 +19,12 @@ module SproutCore
19
19
  include SproutCore::Helpers::DomIdHelper
20
20
  include SproutCore::ViewHelpers
21
21
 
22
- attr_reader :entry, :bundle, :entries, :filename, :language, :library, :renderer
22
+ attr_reader :entry, :bundle, :entries, :filename, :language, :library, :renderer, :unit_test, :platform
23
23
 
24
24
  def initialize(entry, bundle, deep=true)
25
25
  @entry = nil
26
26
  @language = entry.language
27
+ @platform = entry.platform
27
28
  @bundle = bundle
28
29
  @library = bundle.library
29
30
 
@@ -94,12 +95,31 @@ module SproutCore
94
95
 
95
96
  def _render(file_path)
96
97
  SC.logger.debug("~ Rendering #{file_path}")
97
- input = File.read(file_path)
98
+
99
+ # if this is a JS file, read the JS into a unit_test variable then
100
+ # output a special unit_test.rhtml file.
101
+ if file_path =~ /\.js$/
102
+ @unit_test = File.read(file_path)
103
+
104
+ unit_test_template = File.join(File.dirname(__FILE__), 'test_template.rhtml')
105
+ input = File.read(unit_test_template)
106
+
107
+ # Otherwise, just read in the source file
108
+ else
109
+ input = File.read(file_path)
110
+ end
111
+
98
112
  @renderer = case file_path
99
- when /\.rhtml$/, /\.html.erb$/
113
+
114
+ # rhtml & .html.erb get processed through Erubis.
115
+ # JS files passed in for unit tests are also erubis.
116
+ when /\.js$/, /\.rhtml$/, /\.html.erb$/
100
117
  Sproutcore::Renderers::Erubis.new(self)
118
+
119
+ # haml is processed through HAML
101
120
  when /\.haml$/
102
121
  Sproutcore::Renderers::Haml.new(self)
122
+
103
123
  end
104
124
  _render_compiled_template( @renderer.compile(input) )
105
125
  end
@@ -132,9 +152,12 @@ module SproutCore
132
152
 
133
153
  end
134
154
 
135
- # Building a test is just like building a single page except that we do not include
136
- # all the other html templates in the project
137
- def self.build_test(entry, bundle); build_html(entry, bundle, false); end
155
+ # Building a test is almost like building a single page except that we
156
+ # do not include all the other html templates. Also, if the test is a
157
+ # JS file instead, we need to generate the HTML template.
158
+ def self.build_test(entry, bundle)
159
+ build_html(entry, bundle, false)
160
+ end
138
161
 
139
162
  end
140
163
  end
@@ -10,20 +10,23 @@ module SproutCore
10
10
  # The ResourceBuilder knows how
11
11
  class ResourceBuilder
12
12
 
13
- attr_reader :bundle, :language, :filenames
13
+ attr_reader :bundle, :language, :platform, :filenames
14
14
 
15
15
  # Utility method you can call to get the items sorted by load order
16
- def self.sort_entries_by_load_order(entries, language, bundle)
16
+ def self.sort_entries_by_load_order(entries, language, bundle, platform)
17
17
  filenames = entries.map { |e| e.filename }
18
18
  hashed = {}
19
19
  entries.each { |e| hashed[e.filename] = e }
20
20
 
21
- sorted = self.new(filenames, language, bundle).required
21
+ sorted = self.new(filenames, language, bundle, platform).required
22
22
  sorted.map { |filename| hashed[filename] }
23
23
  end
24
24
 
25
- def initialize(filenames, language, bundle)
26
- @bundle = bundle; @language = language; @filenames = filenames
25
+ def initialize(filenames, language, bundle, platform)
26
+ @bundle = bundle
27
+ @language = language
28
+ @platform = platform
29
+ @filenames = filenames
27
30
  end
28
31
 
29
32
  # Simply returns the filenames in the order that they were required
@@ -81,7 +84,7 @@ module SproutCore
81
84
  return [lines, required] if required.include?(filename)
82
85
  required << filename
83
86
 
84
- entry = bundle.entry_for(filename, :hidden => :include, :language => language)
87
+ entry = bundle.entry_for(filename, :hidden => :include, :language => language, :platform => platform)
85
88
  if entry.nil?
86
89
  puts "WARNING: Could not find require file: #{filename}"
87
90
  return [lines, required]
@@ -144,7 +147,6 @@ module SproutCore
144
147
 
145
148
  # Final processing of file. Remove comments & minify
146
149
  def join(lines)
147
-
148
150
  if bundle.minify?
149
151
  # first suck out any comments that should be retained
150
152
  comments = []
@@ -152,20 +154,25 @@ module SproutCore
152
154
  lines.each do | line |
153
155
  is_mark = (line =~ /@license/)
154
156
  unless include_line
155
- include_line = true if is_mark
157
+ if is_mark
158
+ include_line = true
159
+ line= "/*!\n"
160
+ end
156
161
  is_mark = false
157
162
  end
158
-
159
- comments << line if include_line
160
- include_line = false if include_line && is_mark
163
+ if include_line && is_mark
164
+ include_line = false
165
+ comments << "*/\n"
166
+ elsif include_line
167
+ comments << line
168
+ end
161
169
  end
162
-
163
170
  # now minify and prepend any static
164
171
  comments.push "\n" unless comments.empty?
165
- comments.push SproutCore::JSMin.run(lines * '')
172
+ comments.push (lines * '')
166
173
  lines = comments
167
174
  end
168
-
175
+
169
176
  lines.join
170
177
  end
171
178
 
@@ -197,7 +204,7 @@ module SproutCore
197
204
 
198
205
  def self.build_stylesheet(entry, bundle)
199
206
  filenames = entry.composite? ? entry.composite_filenames : [entry.filename]
200
- builder = ResourceBuilder.new(filenames, entry.language, bundle)
207
+ builder = ResourceBuilder.new(filenames, entry.language, bundle, entry.platform)
201
208
  if output = builder.build
202
209
  FileUtils.mkdir_p(File.dirname(entry.build_path))
203
210
  f = File.open(entry.build_path, 'w')
@@ -208,12 +215,24 @@ module SproutCore
208
215
 
209
216
  def self.build_javascript(entry, bundle)
210
217
  filenames = entry.composite? ? entry.composite_filenames : [entry.filename]
211
- builder = JavaScriptResourceBuilder.new(filenames, entry.language, bundle)
218
+ builder = JavaScriptResourceBuilder.new(filenames, entry.language, bundle, entry.platform)
212
219
  if output = builder.build
213
220
  FileUtils.mkdir_p(File.dirname(entry.build_path))
214
221
  f = File.open(entry.build_path, 'w')
215
222
  f.write(output)
216
223
  f.close
224
+ if bundle.minify?
225
+ yui_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'yui_compressor'))
226
+ jar_path = File.join(yui_root, 'yuicompressor-2.4.2.jar')
227
+ filecompress = "java -jar " + jar_path + " --charset utf-8 " + entry.build_path + " -o " +entry.build_path
228
+ puts 'Compressing with YUI .... '+ entry.build_path
229
+ puts `#{filecompress}`
230
+ if $?.exitstatus != 0
231
+ SC.logger.fatal("!!!!YUI compressor failed, please check that your js code is valid and doesn't contain reserved statements like debugger;")
232
+ SC.logger.fatal("!!!!Failed compressing ... "+ entry.build_path)
233
+ exit(1)
234
+ end
235
+ end
217
236
  end
218
237
  end
219
238
 
@@ -0,0 +1,12 @@
1
+ <% # ========================================================================
2
+ # Generic Unit Test Template
3
+ # This template is used when you include a .js file in your unit tests.
4
+ # ========================================================================
5
+ %>
6
+ <% content_for('final') do %>
7
+
8
+ <script>
9
+ <%= unit_test %>
10
+ </script>
11
+
12
+ <% end %>
@@ -1,4 +1,5 @@
1
1
  require 'sproutcore/build_tools'
2
+ require 'digest/md5'
2
3
 
3
4
  module SproutCore
4
5
 
@@ -74,6 +75,10 @@ module SproutCore
74
75
  # MD5 digests instead of timestamps. This will ensure uniqueness when
75
76
  # building on multiple machines.
76
77
  #
78
+ # build_number:: The build number. Defaults to timestamp
79
+ #
80
+ # prefered_platform:: preferred default platform. Default :desktop
81
+ #
77
82
  class Bundle
78
83
 
79
84
  LONG_LANGUAGE_MAP = { :english => :en, :french => :fr, :german => :de, :japanese => :ja, :spanish => :es, :italian => :it }
@@ -94,6 +99,8 @@ module SproutCore
94
99
  attr_reader :build_mode, :layout
95
100
  attr_reader :make_resources_relative
96
101
  attr_reader :use_digest_tokens
102
+
103
+ attr_reader :preferred_platform
97
104
 
98
105
  def library_root
99
106
  @library_root ||= library.nil? ? nil : library.root_path
@@ -131,6 +138,16 @@ module SproutCore
131
138
  @autobuild.nil? ? true : @autobuild
132
139
  end
133
140
 
141
+ # ==== Returns
142
+ # The build number for the bundle. If you supply a build number, that
143
+ # will be used. If not, then the build number will always be
144
+ # 'development' in development mode and a number computed by MD5 hash
145
+ # of the entire bundle contents in production mode.
146
+ def build_number
147
+ return 'development' if build_mode == :development
148
+ return @build_number ||= SC.library.compute_build_number(self)
149
+ end
150
+
134
151
  # ==== Returns
135
152
  # The computed path to the layout rhtml.
136
153
  def layout_path
@@ -236,6 +253,9 @@ module SproutCore
236
253
 
237
254
  @use_digest_tokens = opts[:use_digest_tokens] || (@build_mode == :production)
238
255
 
256
+ @preferred_platform = opts[:preferred_platform] || :desktop
257
+ @build_number = opts[:build_number]
258
+
239
259
  reload!
240
260
  end
241
261
 
@@ -247,16 +267,18 @@ module SproutCore
247
267
  # The array of stylesheet entries sorted in load order.
248
268
  def sorted_stylesheet_entries(opts = {})
249
269
  opts[:language] ||= preferred_language
270
+ opts[:platform] ||= preferred_platform
250
271
  entries = entries_for(:stylesheet, opts)
251
- BuildTools::ResourceBuilder.sort_entries_by_load_order(entries, opts[:language], self)
272
+ BuildTools::ResourceBuilder.sort_entries_by_load_order(entries, opts[:language], self, opts[:platform])
252
273
  end
253
274
 
254
275
  # ==== Returns
255
276
  # The array of javascript entries sorted in load order.
256
277
  def sorted_javascript_entries(opts = {})
257
278
  opts[:language] ||= preferred_language
279
+ opts[:platform] ||= preferred_platform
258
280
  entries = entries_for(:javascript, opts)
259
- BuildTools::JavaScriptResourceBuilder.sort_entries_by_load_order(entries, opts[:language], self)
281
+ BuildTools::JavaScriptResourceBuilder.sort_entries_by_load_order(entries, opts[:language], self, opts[:platform])
260
282
  end
261
283
 
262
284
  # This method returns the manifest entries for resources of the specified
@@ -268,14 +290,16 @@ module SproutCore
268
290
  #
269
291
  # ==== Options
270
292
  # language:: The language to use. Defaults to preferred language.
293
+ # platform:: The platform to use. Defaults to preferred platform.
271
294
  # hidden:: Can be :none|:include|:only
272
295
  #
273
296
  def entries_for(resource_type, opts={})
274
297
  with_hidden = opts[:hidden] || :none
275
298
 
276
299
  language = opts[:language] || preferred_language
300
+ platform = opts[:platform] || preferred_platform
277
301
  mode = (opts[:build_mode] || build_mode).to_sym
278
- manifest = manifest_for(language, mode)
302
+ manifest = manifest_for(language, mode, platform)
279
303
 
280
304
  ret = manifest.entries_for(resource_type)
281
305
 
@@ -295,14 +319,16 @@ module SproutCore
295
319
  #
296
320
  # ==== Options
297
321
  # language:: The language to use. Defaults to preferred language
322
+ # platform:: The platform to use. Defaults to preferred platform.
298
323
  # hidden:: Can be :none|:include|:only
299
324
  #
300
325
  def entry_for(resource_name, opts={})
301
326
  with_hidden = opts[:hidden] || :none
302
327
 
303
328
  language = opts[:language] || preferred_language
329
+ platform = opts[:platform] || preferred_platform
304
330
  mode = (opts[:build_mode] || build_mode).to_sym
305
- manifest = manifest_for(language, mode)
331
+ manifest = manifest_for(language, mode, platform)
306
332
 
307
333
  ret = manifest.entry_for(resource_name)
308
334
 
@@ -316,9 +342,9 @@ module SproutCore
316
342
  end
317
343
 
318
344
  # Returns the entry for the specified URL. This will extract the language
319
- # from the URL and try to get the entry from both the manifest in the
320
- # current build mode and in the production build mode (if one is
321
- # provided)
345
+ # and platform from the URL and try to get the entry from both the
346
+ # manifest in the current build mode and in the production build mode (if
347
+ # one is provided)
322
348
  #
323
349
  # ==== Params
324
350
  # url<String>:: The url
@@ -327,15 +353,21 @@ module SproutCore
327
353
  # hidden:: Use :include,:none,:only to control hidden options
328
354
  # language:: Explicitly include the language. Leave this out to
329
355
  # autodetect from URL.
356
+ # platform:: Explicitly include the platform. Leave this out to
357
+ # autodetect from URL.
330
358
  #
331
359
  def entry_for_url(url, opts={})
360
+
332
361
  # get the language
333
362
  opts[:language] ||= url.match(/^#{url_root}\/([^\/]+)\//).to_a[1] || url.match(/^#{index_root}\/([^\/]+)\//).to_a[1] || preferred_language
334
363
 
364
+ opts[:platform] ||= url.match(/^#{url_root}\/([^\/]+)\/([^\/]+)\//).to_a[2] || url.match(/^#{index_root}\/([^\/]+)\/([^\/]+)\//).to_a[2] || preferred_platform
365
+
335
366
  # use the current build mode
336
367
  opts[:build_mode] = build_mode
337
368
  entries(opts).each do |entry|
338
369
  return entry if entry.url == url
370
+ return entry if entry.current_url == url
339
371
  end
340
372
 
341
373
  # try production is necessary...
@@ -343,6 +375,7 @@ module SproutCore
343
375
  opts[:build_mode] = :production
344
376
  entries(opts).each do |entry|
345
377
  return entry if entry.url == url
378
+ return entry if entry.current_url == url
346
379
  end
347
380
  end
348
381
 
@@ -351,20 +384,24 @@ module SproutCore
351
384
 
352
385
  # Helper method. This will normalize a URL into one that can map directly
353
386
  # to an entry in the bundle. If the URL is of a format that cannot be
354
- # converted, returns nil.
387
+ # converted, returns the url. In particular, this will look for all the
388
+ # different ways you can request an index.html file and convert it to a
389
+ # canonical form
355
390
  #
356
391
  # ==== Params
357
392
  # url<String>:: The URL
358
393
  #
359
394
  def normalize_url(url)
360
395
 
361
- # Get the default index.
362
- if (url == index_root)
363
- url = [index_root, preferred_language.to_s, 'index.html'].join('/')
364
-
365
- # Requests to index_root/lang should have index.html appended to them
366
- elsif /^#{index_root}\/[^\/\.]+$/ =~ url
367
- url << '/index.html'
396
+ # Parse the URL
397
+ matched = url.match(/^#{index_root}(\/([^\/\.]+))?(\/([^\/\.]+))?(\/|(\/index\.html))?$/)
398
+ unless matched.nil?
399
+ matched_language = matched[2] || preferred_language
400
+ matched_platform = matched[4] || preferred_platform
401
+ matched_build_number = matched[6] || 'current'
402
+ url = [index_root,
403
+ matched_language, matched_build_number,
404
+ 'index.html'] * '/'
368
405
  end
369
406
 
370
407
  return url
@@ -374,14 +411,16 @@ module SproutCore
374
411
  #
375
412
  # ==== Options
376
413
  # language:: The language to use. Defaults to preferred language
414
+ # platform:: The platform to use. Defaults to preferred platform
377
415
  # hidden:: Can be :none|:include|:only
378
416
  #
379
417
  def entries(opts ={})
380
418
  with_hidden = opts[:hidden] || :none
381
419
 
382
420
  language = opts[:language] || preferred_language
421
+ platform = opts[:platform] || preferred_platform
383
422
  mode = (opts[:build_mode] || build_mode).to_sym
384
- manifest = manifest_for(language, mode)
423
+ manifest = manifest_for(language, mode, platform)
385
424
 
386
425
  ret = manifest.entries
387
426
 
@@ -500,17 +539,17 @@ module SproutCore
500
539
  @seen << entry unless @seen.nil?
501
540
  end
502
541
 
503
- # This will perform a complete build for the named language
504
- def build_language(language)
505
- SC.logger.info("~ Language: #{language}")
506
- build_entries(entries(:language => language))
542
+ # This will perform a build for a single language
543
+ def build_language(language, platform=nil)
544
+ SC.logger.info("~ Language/Platform: #{[language,platform].compact.join('/') }")
545
+ build_entries(entries(:language => language, :platform => platform))
507
546
  SC.logger.debug("~ Done.\n")
508
547
  end
509
548
 
510
549
  # This will perform a complete build for all languages that have a
511
550
  # matching lproj. You can also pass in an array of languages you would
512
551
  # like to build
513
- def build(*languages)
552
+ def build(platform, *languages)
514
553
 
515
554
  # Get the installed languages (and the preferred language, just in case)
516
555
  languages = languages.flatten
@@ -521,7 +560,7 @@ module SproutCore
521
560
  SC.logger.debug("~ Source Root: #{source_root}")
522
561
  SC.logger.debug("~ Build Root: #{build_root}")
523
562
 
524
- languages.uniq.each { |lang| build_language(lang) }
563
+ languages.uniq.each { |lang| build_language(lang, platform) }
525
564
 
526
565
  # After build is complete, try to copy the index.html file of the
527
566
  # preferred language to the build_root
@@ -679,18 +718,28 @@ module SproutCore
679
718
 
680
719
  # Returns the bundle manifest for the specified language and build mode.
681
720
  # The manifest will be created if it does not yet exist.
682
- def manifest_for(language, build_mode)
683
- manifest_key = [build_mode.to_s, language.to_s].join(':').to_sym
684
- @manifests[manifest_key] ||= BundleManifest.new(self, language.to_sym, build_mode.to_sym)
721
+ def manifest_for(language, build_mode, platform)
722
+ manifest_key = [build_mode.to_s, language.to_s, platform.to_s].join(':').to_sym
723
+ @manifests[manifest_key] ||= BundleManifest.new(self, language.to_sym, build_mode.to_sym, platform.to_sym)
685
724
  end
686
725
 
726
+ # ==== Returns
727
+ # Platforms installed in the source directory
728
+ #
729
+ def installed_platforms
730
+ ret = Dir.glob(File.join(source_root, '*.platform')).map do |x|
731
+ x.match(/([^\/]+)\.platform$/).to_a[1]
732
+ end
733
+ ret << preferred_platform
734
+ ret.compact.map { |x| x.to_sym }.uniq
735
+ end
736
+
687
737
  # ==== Returns
688
738
  # Languages installed in the source directory
689
739
  #
690
740
  def installed_languages
691
- ret = Dir.glob(File.join(source_root,'*.lproj')).map do |x|
692
- x.match(/([^\/]+)\.lproj$/).to_a[1]
693
- end
741
+ ret = Dir.glob(File.join(source_root,'*.lproj')) + Dir.glob(File.join(source_root, '*.platform', '*.lproj'))
742
+ ret.map! { |x| x.match(/([^\/]+)\.lproj$/).to_a[1] }
694
743
  ret << preferred_language
695
744
  ret.compact.map { |x| LONG_LANGUAGE_MAP[x.to_sym] || x.to_sym }.uniq
696
745
  end
@@ -6,21 +6,30 @@ module SproutCore
6
6
  # A Bundle Manifest describes all of the resources in a bundle, including
7
7
  # mapping their source paths, destination paths, and urls.
8
8
  #
9
- # A Bundle will create a manifest for every language you request from it.
10
- # If you invoke reload! on the bundle, it will dispose of its manifests and
11
- # rebuild them.
9
+ # A Bundle will create a manifest for every language and platform you
10
+ # request from it. If you invoke reload! on the bundle, it will dispose of
11
+ # its manifests and rebuild them.
12
12
  #
13
13
  class BundleManifest
14
14
 
15
- CACHED_TYPES = [:javascript, :stylesheet, :fixture, :test]
15
+ CACHED_TYPES = [:javascript, :stylesheet, :fixture, :test]
16
16
  SYMLINKED_TYPES = [:resource]
17
+
18
+ PLATFORM_MATCH = /^([^\/]+)\.platform\//
19
+ LPROJ_MATCH = /^([^\/]+\.platform\/)?([^\/]+\.lproj)\//
20
+
21
+ NORMALIZED_TYPE_EXTENSIONS = {
22
+ :stylesheet => { :sass => :css },
23
+ :test => { '[^\/\.]+' => :html }
24
+ }
17
25
 
18
- attr_reader :bundle, :language, :build_mode
26
+ attr_reader :bundle, :language, :build_mode, :platform
19
27
 
20
- def initialize(bundle, language, build_mode)
28
+ def initialize(bundle, language, build_mode, platform)
21
29
  @bundle = bundle
22
30
  @language = language
23
31
  @build_mode = build_mode
32
+ @platform = platform
24
33
  @entries_by_type = {} # entries by type
25
34
  @entries_by_filename = {} # entries by files
26
35
  build!
@@ -168,7 +177,8 @@ module SproutCore
168
177
  end
169
178
 
170
179
  # Build a catalog of entries for this manifest. This will simply filter
171
- # out the files that don't actually belong in the current language
180
+ # out the files that don't actually belong in the current language or
181
+ # platform
172
182
  def catalog_entries
173
183
 
174
184
  # Entries arranged by resource filename
@@ -180,8 +190,9 @@ module SproutCore
180
190
  default_lproj = bundle.lproj_for(bundle.preferred_language)
181
191
  target_lproj = bundle.lproj_for(language)
182
192
 
183
- # Any files living in the two lproj dirs will be shunted off into these arrays
184
- # and processed later to make sure we process them in the right order
193
+ # Any files living in the two lproj dirs will be shunted off into these
194
+ # arrays and processed later to make sure we process them in the right
195
+ # order
185
196
  default_lproj_files = []
186
197
  target_lproj_files = []
187
198
 
@@ -192,33 +203,46 @@ module SproutCore
192
203
  # Get source type. Skip any without a useful type
193
204
  next if (src_type = type_of(src_path)) == :skip
194
205
 
206
+ # Get target platform (if there is one). Skip is not target platform
207
+ if current_platform = src_path.match(PLATFORM_MATCH).to_a[1]
208
+ next if current_platform.to_sym != platform
209
+ end
210
+
195
211
  # Get current lproj (if there is one). Skip if not default or current
196
- if current_lproj = src_path.match(/^([^\/]+\.lproj)\//).to_a[1]
212
+ if current_lproj = src_path.match(LPROJ_MATCH).to_a[2]
197
213
  next if (current_lproj != default_lproj) && (current_lproj != target_lproj)
198
214
  end
199
215
 
200
- # OK, pass all of our validations. Go ahead and build an entry for this
201
- # Add entry to list of entries for appropriate lproj if localized
216
+ # OK, pass all of our validations. Go ahead and build an entry for
217
+ # this. Add entry to list of entries for appropriate lproj if
218
+ # localized.
219
+ #
220
+ # Note that entries are namespaced by platform. This way non-platform
221
+ # specific entries will not be overwritten by platfor-specific entries
222
+ # of the same name.
223
+ #
202
224
  entry = build_entry_for(src_path, src_type)
225
+ entry_key = [current_platform||'', entry.filename].join(':')
203
226
  case current_lproj
204
227
  when default_lproj
205
- default_lproj_entries[entry.filename] = entry
228
+ default_lproj_entries[entry_key] = entry
206
229
  when target_lproj
207
- target_lproj_entries[entry.filename] = entry
230
+ target_lproj_entries[entry_key] = entry
208
231
  else
209
232
 
210
233
  # Be sure to mark any
211
- entries[entry.filename] = entry
234
+ entries[entry_key] = entry
212
235
  end
213
236
  end
214
237
  Dir.chdir(old_wd) # restore wd
215
238
 
216
- # Now, new in default and target lproj entries. This will overwrite entries that exist
217
- # in both places.
239
+ # Now, merge in default and target lproj entries. This will overwrite
240
+ # entries that exist in both places.
218
241
  entries.merge!(default_lproj_entries)
219
242
  entries.merge!(target_lproj_entries)
220
243
 
221
- # Finally, entries will need to be grouped by type to allow further processing.
244
+ # Finally, entries will need to be grouped by type to allow further
245
+ # processing.
222
246
  ret = {}
223
247
  entries.values.each { |entry| (ret[entry.type] ||= []) << entry }
224
248
  return ret
@@ -246,11 +270,11 @@ module SproutCore
246
270
  def type_of(src_path)
247
271
  return :skip if File.directory?(src_path)
248
272
  case src_path
249
- when /^tests\/.+/
273
+ when /^([^\/\.]+\.platform\/)?tests\/.+/
250
274
  :test
251
- when /^fixtures\/.+\.js$/
275
+ when /^([^\/\.]+\.platform\/)?fixtures\/.+\.js$/
252
276
  :fixture
253
- when /^debug\/.+\.js$/
277
+ when /^([^\/\.]+\.platform\/)?debug\/.+\.js$/
254
278
  :debug
255
279
  when /\.rhtml$/
256
280
  :html
@@ -275,6 +299,16 @@ module SproutCore
275
299
  # source_root) This should assume we are in going to simply build each
276
300
  # resource into the build root without combining files, but not using our
277
301
  # _src symlink magic.
302
+ #
303
+ # +Params+
304
+ #
305
+ # src_path:: the source path, relative to the bunlde.source_root
306
+ # src_type:: the detected source type (from type_of())
307
+ # composite:: Array of entries that should be combined to form this or nil
308
+ # hide_composite:: Makes composit entries hidden if !composite.nil?
309
+ #
310
+ # +Returns: Entry
311
+ #
278
312
  def build_entry_for(src_path, src_type, composite=nil, hide_composite = true)
279
313
  ret = ManifestEntry.new
280
314
  ret.ext = File.extname(src_path)[1..-1] || '' # easy stuff
@@ -282,17 +316,20 @@ module SproutCore
282
316
  ret.original_path = src_path
283
317
  ret.hidden = false
284
318
  ret.language = language
319
+ ret.platform = platform
285
320
  ret.use_digest_tokens = bundle.use_digest_tokens
286
321
 
287
- # the filename is the src_path less any lproj in the front
288
- ret.filename = src_path.gsub(/^[^\/]+.lproj\//,'')
322
+ # the filename is the src_path less any lproj or platform in the front
323
+ ret.filename = src_path.gsub(LPROJ_MATCH,'')
289
324
 
290
325
  # the source path is just the combine source root + the path
291
- ret.source_path = (composite.nil?) ? File.join(bundle.source_root, src_path) : nil
326
+ # Composite entries do not have a source path b/c they are generated
327
+ # dynamically.
328
+ ret.source_path = composite.nil? ? File.join(bundle.source_root, src_path) : nil
292
329
 
293
330
  # set the composite property. The passed in array should contain other
294
331
  # entries if hide_composite is true, then hide the composite items as
295
- # well
332
+ # well.
296
333
  unless composite.nil?
297
334
  composite.each { |x| x.hidden = true } if hide_composite
298
335
 
@@ -303,45 +340,77 @@ module SproutCore
303
340
  ret.composite = composite.dup
304
341
  end
305
342
 
306
- # The build path is the build_root + the filename
307
- # The URL is the url root + the language code + filename
308
- # also add in _cache or _sym in certain cases. This is just more
309
- # efficient than doing it later.
343
+ # PREPARE BUILD_PATH and URL
344
+
345
+ # The main index.html file is served from the index_Root. All other
346
+ # resourced are served from the URL root.
310
347
  url_root = (src_path == 'index.html') ? bundle.index_root : bundle.url_root
311
- cache_link = nil
312
- use_source_directly =false
313
348
 
314
- # Note: you can only access real resources via the cache. If the entry
315
- # is a composite then do not go through cache.
349
+ # Setup special cases. Certain types of files are processed and then
350
+ # cached in development mode (i.e. JS + CSS). Other resources are
351
+ # simply served up directly without any processing or building. See
352
+ # constants for types.
353
+ cache_link = nil; use_source_directly =false
316
354
  if (self.build_mode == :development) #&& composite.nil?
317
355
  cache_link = '_cache' if CACHED_TYPES.include?(src_type)
318
356
  use_source_directly = true if SYMLINKED_TYPES.include?(src_type)
319
357
  end
320
358
 
359
+ # If this resource should be served directly, setup both the build_path
360
+ # and URL to point to a special URL that maps directly to the resource.
361
+ # This is only useful in development mode
321
362
  ret.use_source_directly = use_source_directly
322
363
  if use_source_directly
323
- ret.build_path = File.join(bundle.build_root, '_src', src_path)
324
- ret.url = [url_root, '_src', src_path].join('/')
364
+ path_parts = [bundle.build_root, language.to_s, '_src', src_path]
365
+ ret.build_path = File.join(*(path_parts.compact))
366
+ path_parts[0] = url_root
367
+ ret.url = path_parts.compact.join('/')
368
+
369
+ # If the resource is not served directly, then calculate the actual
370
+ # build path and URL for production mode. The build path is the
371
+ # build root + language + platform + (cache_link || build_number) +
372
+ # filename
373
+ #
374
+ # The URL is the url_root + current_language + current_platform + (cache_link)
325
375
  else
326
- ret.build_path = File.join(*[bundle.build_root, language.to_s, cache_link, ret.filename].compact)
327
- ret.url = [url_root, language.to_s, cache_link, ret.filename].compact.join('/')
376
+ path_parts = [bundle.build_root, language.to_s,
377
+ (cache_link || bundle.build_number.to_s), ret.filename]
378
+ ret.build_path = File.join(*path_parts.compact)
379
+
380
+ path_parts[0] = url_root
381
+ ret.url = path_parts.compact.join('/')
382
+
383
+ path_parts[2] = 'current' # create path to "current" build
384
+ ret.current_url = path_parts.compact.join('/')
385
+
386
+ end
387
+
388
+ # Convert the input source type an output type.
389
+ if sub_type = NORMALIZED_TYPE_EXTENSIONS[ret.type]
390
+ sub_type.each do | matcher, ext |
391
+ matcher = /\.#{matcher.to_s}$/; ext = ".#{ext.to_s}"
392
+ ret.build_path.sub!(matcher, ext)
393
+ ret.url.sub!(matcher, ext)
394
+ end
328
395
  end
329
- ret.build_path.sub!(/\.sass$/, '.css')
330
- ret.url.sub!(/.sass$/, '.css')
331
396
 
332
397
  # Done.
333
398
  return ret
334
399
  end
335
400
 
336
- # Lookup the timestamp on the source path and interpolate that into the filename URL.
337
- # also insert the _cache element.
401
+ # Lookup the timestamp on the source path and interpolate that into the
402
+ # filename URL and build path. This should only be called on entries
403
+ # that are to be cached (in development mode)
338
404
  def setup_timestamp_token(entry)
339
405
  timestamp = bundle.use_digest_tokens ? entry.digest : entry.timestamp
406
+
407
+ # add timestamp or digest to URL
340
408
  extname = File.extname(entry.url)
341
- entry.url = entry.url.gsub(/#{extname}$/,"-#{timestamp}#{extname}") # add timestamp
409
+ entry.url.gsub!(/#{extname}$/,"-#{timestamp}#{extname}")
342
410
 
411
+ # add timestamp or digest to build path
343
412
  extname = File.extname(entry.build_path)
344
- entry.build_path = entry.build_path.gsub(/#{extname}$/,"-#{timestamp}#{extname}") # add timestamp
413
+ entry.build_path.gsub!(/#{extname}$/,"-#{timestamp}#{extname}")
345
414
  end
346
415
  end
347
416
 
@@ -350,7 +419,8 @@ module SproutCore
350
419
  # filename:: path relative to the built language (e.g. sproutcore/en) less file extension
351
420
  # ext:: the file extension
352
421
  # source_path:: absolute paths into source that will comprise this resource
353
- # url:: the url that should be used to reference this resource in the current mode.
422
+ # url:: the url that should be used to reference this resource in the current build mode.
423
+ # current_url:: the url that can be used to reference this resource, substituting "current" for a build number
354
424
  # build_path:: absolute path to the compiled resource
355
425
  # type:: the top-level category
356
426
  # original_path:: save the original path used to build this entry
@@ -359,8 +429,9 @@ module SproutCore
359
429
  # language:: the language in use when this entry was created
360
430
  # composite:: If set, this will contain the filenames of other resources that should be combined to form this resource.
361
431
  # bundle:: the owner bundle for this entry
432
+ # platform:: the target platform for the entry, if any
362
433
  #
363
- class ManifestEntry < Struct.new(:filename, :ext, :source_path, :url, :build_path, :type, :original_path, :hidden, :use_source_directly, :language, :use_digest_tokens)
434
+ class ManifestEntry < Struct.new(:filename, :ext, :source_path, :url, :build_path, :type, :original_path, :hidden, :use_source_directly, :language, :use_digest_tokens, :platform, :current_url)
364
435
  def to_hash
365
436
  ret = {}
366
437
  self.members.zip(self.values).each { |p| ret[p[0]] = p[1] }
@@ -431,8 +502,9 @@ module SproutCore
431
502
 
432
503
  # Returns a URL that takes into account caching requirements.
433
504
  def cacheable_url
434
- token = (use_digest_tokens) ? digest : timestamp
435
- [url, token].compact.join('?')
505
+ url
506
+ #token = (use_digest_tokens) ? digest : timestamp
507
+ #[url, token].compact.join('?')
436
508
  end
437
509
 
438
510
  # :stopdoc:
@@ -492,6 +564,8 @@ module SproutCore
492
564
  "xpm" => "image/x-xpixmap",
493
565
  "xwd" => "image/x-xwindowdump",
494
566
  "zip" => "application/zip",
567
+ "js" => "text/javascript",
568
+ "json" => "text/json"
495
569
  }
496
570
  # :startdoc:
497
571
 
@@ -18,6 +18,7 @@ module SproutCore
18
18
  def stylesheets_for_client(bundle_name = nil, opts = {})
19
19
 
20
20
  opts[:language] ||= language
21
+ opts[:platform] ||= platform
21
22
 
22
23
  # Set the import method to use the standard <link> tag, if not set
23
24
  include_method = opts[:include_method] ||= :link
@@ -64,6 +65,7 @@ module SproutCore
64
65
  def javascripts_for_client(bundle_name = nil, opts = {})
65
66
 
66
67
  opts[:language] ||= language
68
+ opts[:platform] ||= platform
67
69
 
68
70
  # Get bundle
69
71
  cur_bundle = bundle_name.nil? ? bundle : library.bundle_for(bundle_name)
@@ -94,6 +96,7 @@ module SproutCore
94
96
  # Returns the URL for the named resource
95
97
  def static_url(resource_name, opts = {})
96
98
  opts[:language] ||= language
99
+ opts[:platform] ||= platform
97
100
  entry = bundle.find_resource_entry(resource_name, opts)
98
101
  entry.nil? ? '' : entry.cacheable_url
99
102
  end
@@ -102,6 +105,7 @@ module SproutCore
102
105
  def loc(string, opts = {})
103
106
  string = string.nil? ? '' : string.to_s
104
107
  opts[:language] ||= language
108
+ opts[:platform] ||= platform
105
109
  bundle.strings_hash(opts)[string] || string
106
110
  end
107
111
 
@@ -348,6 +348,21 @@ module SproutCore
348
348
  self.bundle_installer.remove(bundle_name, opts)
349
349
  end
350
350
 
351
+ # Computes a build number for the bundle by generating a SHA1 hash. The
352
+ # return value is cached to speed up future requests.
353
+ def compute_build_number(bundle)
354
+ @cached_build_numbers ||= {}
355
+ src = bundle.source_root
356
+ ret = @cached_build_numbers[src]
357
+ return ret unless ret.nil?
358
+
359
+ digests = Dir.glob(File.join(src, '**', '*.*')).map do |path|
360
+ (File.exists?(path) && !File.directory?(path)) ? Digest::SHA1.hexdigest(File.read(path)) : '0000'
361
+ end
362
+ ret = @cached_build_numbers[src] = Digest::SHA1.hexdigest(digests.join)
363
+ return ret
364
+ end
365
+
351
366
  protected
352
367
 
353
368
  # Load the library at the specified path. Loads the sc-config.rb if it
@@ -379,6 +394,14 @@ module SproutCore
379
394
  end
380
395
 
381
396
  # Override any all options with load_opts
397
+ if build_numbers = @load_opts[:build_numbers]
398
+ @load_opts.delete(:build_numbers)
399
+ build_numbers.each do | bundle_name, build_number |
400
+ env = @environment[bundle_name.to_sym] ||= {}
401
+ env[:build_number] = build_number
402
+ end
403
+ end
404
+
382
405
  (@environment[:all] ||= {}).merge!(@load_opts)
383
406
 
384
407
  end
@@ -1,6 +1,7 @@
1
1
  require 'sproutcore/jsdoc'
2
2
  require 'net/http'
3
3
  require 'uri'
4
+ require 'rack/utils'
4
5
 
5
6
  module SproutCore
6
7
 
@@ -69,8 +70,11 @@ module SproutCore
69
70
 
70
71
  # Check for a few special urls for built-in services and route them off
71
72
  case url
73
+
74
+ # Returns the JSON file that contains the current list of tests.
72
75
  when "#{current_bundle.url_root}/-tests/index.js"
73
76
  ret = handle_test(url)
77
+
74
78
  when "#{current_bundle.index_root}/-docs/index.html"
75
79
  ret = (request.method == :post) ? handle_doc(url) : handle_resource(url)
76
80
 
@@ -2,7 +2,7 @@ module SproutCore #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 9
5
- TINY = 20
5
+ TINY = 21
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -62,7 +62,7 @@ module SproutCore
62
62
  %{#{key} = #{opts[:class] || 'SC.View'}.extend({\n #{ opts[:properties] }\n});}
63
63
  end
64
64
  ret << %{#{prefix}.page = SC.Page.create({\n#{ outlets * ",\n\n" }\n}); }
65
- bundle ? SproutCore::BuildTools::JavaScriptResourceBuilder.new(nil, nil, bundle).join(ret) : ret*"\n"
65
+ bundle ? SproutCore::BuildTools::JavaScriptResourceBuilder.new(nil, nil, bundle, nil).join(ret) : ret*"\n"
66
66
  end
67
67
 
68
68
  def self.render_css
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sproutcore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.20
4
+ version: 0.9.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Jolley, Erich Ocean
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-10-18 00:00:00 -07:00
12
+ date: 2009-01-08 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -60,11 +60,11 @@ dependencies:
60
60
  requirements:
61
61
  - - ">="
62
62
  - !ruby/object:Gem::Version
63
- version: 1.7.0
63
+ version: 1.8.0
64
64
  version:
65
65
  description: SproutCore - JavaScript Application Framework + Build Tools
66
66
  email:
67
- - charles@sproutit.com, erich@atlasocean.com
67
+ - charles@sproutit.com
68
68
  executables:
69
69
  - sc-build
70
70
  - sc-gen
@@ -426,6 +426,7 @@ files:
426
426
  - jsdoc/test.js
427
427
  - lib/sproutcore/build_tools/html_builder.rb
428
428
  - lib/sproutcore/build_tools/resource_builder.rb
429
+ - lib/sproutcore/build_tools/test_template.rhtml
429
430
  - lib/sproutcore/build_tools.rb
430
431
  - lib/sproutcore/bundle.rb
431
432
  - lib/sproutcore/bundle_installer.rb
@@ -498,6 +499,7 @@ files:
498
499
  - tasks/environment.rake
499
500
  - tasks/rspec.rake
500
501
  - tasks/website.rake
502
+ - yui_compressor/yuicompressor-2.4.2.jar
501
503
  has_rdoc: true
502
504
  homepage: http://sproutcore.rubyforge.org
503
505
  post_install_message:
@@ -521,7 +523,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
521
523
  requirements: []
522
524
 
523
525
  rubyforge_project: sproutcore
524
- rubygems_version: 1.2.0
526
+ rubygems_version: 1.3.1
525
527
  signing_key:
526
528
  specification_version: 2
527
529
  summary: SproutCore - JavaScript Application Framework + Build Tools