sproutcore 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +269 -0
- data/README.txt +67 -0
- data/Rakefile +4 -0
- data/app_generators/sproutcore/USAGE +5 -0
- data/app_generators/sproutcore/sproutcore_generator.rb +66 -0
- data/app_generators/sproutcore/templates/README +77 -0
- data/app_generators/sproutcore/templates/environment.yml +4 -0
- data/bin/sc-build +145 -0
- data/bin/sc-gen +24 -0
- data/bin/sc-server +63 -0
- data/bin/sproutcore +21 -0
- data/clients/sc_docs/controllers/docs.js +118 -0
- data/clients/sc_docs/core.js +19 -0
- data/clients/sc_docs/english.lproj/body.css +159 -0
- data/clients/sc_docs/english.lproj/body.rhtml +33 -0
- data/clients/sc_docs/english.lproj/controls.css +0 -0
- data/clients/sc_docs/english.lproj/icons/small/next.png +0 -0
- data/clients/sc_docs/english.lproj/icons/small/reset.png +0 -0
- data/clients/sc_docs/english.lproj/images/gradients.png +0 -0
- data/clients/sc_docs/english.lproj/images/indicator.gif +0 -0
- data/clients/sc_docs/english.lproj/images/toolbar.png +0 -0
- data/clients/sc_docs/english.lproj/no_docs.rhtml +7 -0
- data/clients/sc_docs/english.lproj/strings.js +14 -0
- data/clients/sc_docs/english.lproj/warning.rhtml +6 -0
- data/clients/sc_docs/fixtures/doc.js +11 -0
- data/clients/sc_docs/main.js +21 -0
- data/clients/sc_docs/models/doc.js +9 -0
- data/clients/sc_docs/tests/controllers/docs.rhtml +21 -0
- data/clients/sc_docs/tests/models/doc.rhtml +21 -0
- data/clients/sc_docs/tests/views/doc_frame.rhtml +21 -0
- data/clients/sc_docs/tests/views/doc_label_view.rhtml +21 -0
- data/clients/sc_docs/views/doc_frame.js +33 -0
- data/clients/sc_docs/views/doc_label.js +20 -0
- data/clients/sc_test_runner/controllers/runner.js +175 -0
- data/clients/sc_test_runner/core.js +19 -0
- data/clients/sc_test_runner/english.lproj/body.css +151 -0
- data/clients/sc_test_runner/english.lproj/body.rhtml +35 -0
- data/clients/sc_test_runner/english.lproj/controls.css +0 -0
- data/clients/sc_test_runner/english.lproj/icons/small/next.png +0 -0
- data/clients/sc_test_runner/english.lproj/icons/small/reset.png +0 -0
- data/clients/sc_test_runner/english.lproj/images/gradients.png +0 -0
- data/clients/sc_test_runner/english.lproj/images/indicator.gif +0 -0
- data/clients/sc_test_runner/english.lproj/images/toolbar.png +0 -0
- data/clients/sc_test_runner/english.lproj/no_tests.rhtml +6 -0
- data/clients/sc_test_runner/english.lproj/strings.js +14 -0
- data/clients/sc_test_runner/english.lproj/warning.rhtml +6 -0
- data/clients/sc_test_runner/fixtures/test.js +12 -0
- data/clients/sc_test_runner/main.js +26 -0
- data/clients/sc_test_runner/models/test.js +11 -0
- data/clients/sc_test_runner/views/runner_frame.js +72 -0
- data/clients/sc_test_runner/views/test_label.js +20 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +17 -0
- data/environment.yml +9 -0
- data/frameworks/prototype/prototype.js +4186 -0
- data/frameworks/sproutcore/Core.js +378 -0
- data/frameworks/sproutcore/README +3 -0
- data/frameworks/sproutcore/controllers/array.js +236 -0
- data/frameworks/sproutcore/controllers/collection.js +305 -0
- data/frameworks/sproutcore/controllers/controller.js +323 -0
- data/frameworks/sproutcore/controllers/object.js +372 -0
- data/frameworks/sproutcore/drag/drag.js +549 -0
- data/frameworks/sproutcore/drag/drag_data_source.js +32 -0
- data/frameworks/sproutcore/drag/drag_source.js +64 -0
- data/frameworks/sproutcore/drag/drop_target.js +153 -0
- data/frameworks/sproutcore/english.lproj/blank.gif +0 -0
- data/frameworks/sproutcore/english.lproj/buttons.css +589 -0
- data/frameworks/sproutcore/english.lproj/buttons.png +0 -0
- data/frameworks/sproutcore/english.lproj/inline_text_editor.css +21 -0
- data/frameworks/sproutcore/english.lproj/menu.css +121 -0
- data/frameworks/sproutcore/english.lproj/panels/background-fat.jpg +0 -0
- data/frameworks/sproutcore/english.lproj/panels/background-thin.jpg +0 -0
- data/frameworks/sproutcore/english.lproj/panels/bottom-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/bottom-left-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/bottom-right-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/left-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/overlay.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/right-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/top-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/top-left-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/top-right-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panes.css +155 -0
- data/frameworks/sproutcore/english.lproj/picker.css +22 -0
- data/frameworks/sproutcore/english.lproj/strings.js +15 -0
- data/frameworks/sproutcore/english.lproj/tab.css +23 -0
- data/frameworks/sproutcore/english.lproj/tests.css +67 -0
- data/frameworks/sproutcore/english.lproj/theme.css +77 -0
- data/frameworks/sproutcore/foundation/animator.js +670 -0
- data/frameworks/sproutcore/foundation/application.js +199 -0
- data/frameworks/sproutcore/foundation/array.js +348 -0
- data/frameworks/sproutcore/foundation/benchmark.js +211 -0
- data/frameworks/sproutcore/foundation/binding.js +384 -0
- data/frameworks/sproutcore/foundation/date.js +357 -0
- data/frameworks/sproutcore/foundation/error.js +39 -0
- data/frameworks/sproutcore/foundation/input_manager.js +153 -0
- data/frameworks/sproutcore/foundation/json.js +296 -0
- data/frameworks/sproutcore/foundation/mock.js +42 -0
- data/frameworks/sproutcore/foundation/node_descriptor.js +56 -0
- data/frameworks/sproutcore/foundation/object.js +777 -0
- data/frameworks/sproutcore/foundation/observable.js +451 -0
- data/frameworks/sproutcore/foundation/page.js +63 -0
- data/frameworks/sproutcore/foundation/path_module.js +413 -0
- data/frameworks/sproutcore/foundation/responder.js +310 -0
- data/frameworks/sproutcore/foundation/routes.js +371 -0
- data/frameworks/sproutcore/foundation/run_loop.js +21 -0
- data/frameworks/sproutcore/foundation/server.js +491 -0
- data/frameworks/sproutcore/foundation/set.js +96 -0
- data/frameworks/sproutcore/foundation/string.js +149 -0
- data/frameworks/sproutcore/foundation/undo_manager.js +186 -0
- data/frameworks/sproutcore/foundation/unittest.js +622 -0
- data/frameworks/sproutcore/foundation/utils.js +61 -0
- data/frameworks/sproutcore/globals/panels.js +182 -0
- data/frameworks/sproutcore/globals/popups.js +60 -0
- data/frameworks/sproutcore/globals/window.js +381 -0
- data/frameworks/sproutcore/lib/index.rhtml +66 -0
- data/frameworks/sproutcore/models/collection.js +395 -0
- data/frameworks/sproutcore/models/record.js +622 -0
- data/frameworks/sproutcore/models/store.js +295 -0
- data/frameworks/sproutcore/panes/dialog.js +16 -0
- data/frameworks/sproutcore/panes/manager.js +164 -0
- data/frameworks/sproutcore/panes/menu.js +45 -0
- data/frameworks/sproutcore/panes/overlay.js +231 -0
- data/frameworks/sproutcore/panes/pane.js +90 -0
- data/frameworks/sproutcore/panes/panel.js +19 -0
- data/frameworks/sproutcore/panes/picker.js +45 -0
- data/frameworks/sproutcore/tests/controllers/array.rhtml +86 -0
- data/frameworks/sproutcore/tests/controllers/controller.rhtml +273 -0
- data/frameworks/sproutcore/tests/controllers/object.rhtml +327 -0
- data/frameworks/sproutcore/tests/foundation/application.rhtml +125 -0
- data/frameworks/sproutcore/tests/foundation/array.rhtml +221 -0
- data/frameworks/sproutcore/tests/foundation/object.rhtml +69 -0
- data/frameworks/sproutcore/tests/globals/window.rhtml +45 -0
- data/frameworks/sproutcore/tests/panes/pane.rhtml +88 -0
- data/frameworks/sproutcore/tests/views/collection.rhtml +137 -0
- data/frameworks/sproutcore/tests/views/popup_button.rhtml +115 -0
- data/frameworks/sproutcore/tests/views/text_field.rhtml +37 -0
- data/frameworks/sproutcore/validators/credit_card.js +92 -0
- data/frameworks/sproutcore/validators/date.js +36 -0
- data/frameworks/sproutcore/validators/email.js +29 -0
- data/frameworks/sproutcore/validators/not_empty.js +24 -0
- data/frameworks/sproutcore/validators/number.js +55 -0
- data/frameworks/sproutcore/validators/password.js +78 -0
- data/frameworks/sproutcore/validators/validator.js +304 -0
- data/frameworks/sproutcore/views/button.js +425 -0
- data/frameworks/sproutcore/views/checkbox_field.js +30 -0
- data/frameworks/sproutcore/views/collection.js +1521 -0
- data/frameworks/sproutcore/views/container.js +62 -0
- data/frameworks/sproutcore/views/error_explanation.js +45 -0
- data/frameworks/sproutcore/views/field.js +214 -0
- data/frameworks/sproutcore/views/filter_button.js +29 -0
- data/frameworks/sproutcore/views/form.js +591 -0
- data/frameworks/sproutcore/views/image.js +141 -0
- data/frameworks/sproutcore/views/inline_text_editor.js +96 -0
- data/frameworks/sproutcore/views/label.js +176 -0
- data/frameworks/sproutcore/views/menu_item.js +90 -0
- data/frameworks/sproutcore/views/pagination.js +54 -0
- data/frameworks/sproutcore/views/popup_button.js +86 -0
- data/frameworks/sproutcore/views/popup_menu.js +137 -0
- data/frameworks/sproutcore/views/progress.js +100 -0
- data/frameworks/sproutcore/views/radio_field.js +107 -0
- data/frameworks/sproutcore/views/radio_group.js +48 -0
- data/frameworks/sproutcore/views/segmented.js +80 -0
- data/frameworks/sproutcore/views/select_field.js +272 -0
- data/frameworks/sproutcore/views/spinner.js +11 -0
- data/frameworks/sproutcore/views/tab.js +126 -0
- data/frameworks/sproutcore/views/text_field.js +179 -0
- data/frameworks/sproutcore/views/textarea_field.js +14 -0
- data/frameworks/sproutcore/views/toolbar.js +29 -0
- data/frameworks/sproutcore/views/view.js +1389 -0
- data/frameworks/sproutcore/views/workspace.js +170 -0
- data/generators/client/README +3 -0
- data/generators/client/USAGE +12 -0
- data/generators/client/client_generator.rb +53 -0
- data/generators/client/templates/core.js +19 -0
- data/generators/client/templates/english.lproj/body.css +0 -0
- data/generators/client/templates/english.lproj/body.rhtml +3 -0
- data/generators/client/templates/english.lproj/controls.css +0 -0
- data/generators/client/templates/english.lproj/strings.js +14 -0
- data/generators/client/templates/main.js +37 -0
- data/generators/controller/USAGE +16 -0
- data/generators/controller/controller_generator.rb +51 -0
- data/generators/controller/templates/controller.js +21 -0
- data/generators/controller/templates/test.rhtml +21 -0
- data/generators/framework/README +7 -0
- data/generators/framework/USAGE +12 -0
- data/generators/framework/framework_generator.rb +53 -0
- data/generators/framework/templates/core.js +20 -0
- data/generators/framework/templates/english.lproj/body.css +0 -0
- data/generators/framework/templates/english.lproj/body.rhtml +3 -0
- data/generators/framework/templates/english.lproj/controls.css +0 -0
- data/generators/framework/templates/english.lproj/strings.js +14 -0
- data/generators/language/USAGE +16 -0
- data/generators/language/language_generator.rb +47 -0
- data/generators/language/templates/strings.js +10 -0
- data/generators/model/USAGE +24 -0
- data/generators/model/model_generator.rb +55 -0
- data/generators/model/templates/fixture.js +11 -0
- data/generators/model/templates/model.js +20 -0
- data/generators/model/templates/test.rhtml +21 -0
- data/generators/test/USAGE +16 -0
- data/generators/test/templates/test.rhtml +21 -0
- data/generators/test/test_generator.rb +47 -0
- data/generators/view/USAGE +16 -0
- data/generators/view/templates/test.rhtml +21 -0
- data/generators/view/templates/view.js +20 -0
- data/generators/view/view_generator.rb +51 -0
- data/jsdoc/README.txt +119 -0
- data/jsdoc/app/DocFile.js +137 -0
- data/jsdoc/app/DocTag.js +110 -0
- data/jsdoc/app/Doclet.js +63 -0
- data/jsdoc/app/Dumper.js +143 -0
- data/jsdoc/app/JsDoc.js +103 -0
- data/jsdoc/app/JsHilite.js +45 -0
- data/jsdoc/app/JsIO.js +163 -0
- data/jsdoc/app/JsParse.js +385 -0
- data/jsdoc/app/JsPlate.js +130 -0
- data/jsdoc/app/JsTestrun.js +129 -0
- data/jsdoc/app/JsToke.js +564 -0
- data/jsdoc/app/Symbol.js +298 -0
- data/jsdoc/app/Transformer.js +14 -0
- data/jsdoc/app/Util.js +97 -0
- data/jsdoc/app/js.jar +0 -0
- data/jsdoc/app/run.js +144 -0
- data/jsdoc/plugins/min.js +316 -0
- data/jsdoc/plugins/strip.js +20 -0
- data/jsdoc/templates/sproutcore/class.tmpl +438 -0
- data/jsdoc/templates/sproutcore/default.css +241 -0
- data/jsdoc/templates/sproutcore/index.html +13 -0
- data/jsdoc/templates/sproutcore/index.tmpl +21 -0
- data/jsdoc/templates/sproutcore/prototype.js +4186 -0
- data/jsdoc/templates/sproutcore/publish.js +236 -0
- data/jsdoc/templates/sproutcore/splash.html +7 -0
- data/lib/sproutcore/build_tools/html_builder.rb +88 -0
- data/lib/sproutcore/build_tools/resource_builder.rb +194 -0
- data/lib/sproutcore/build_tools.rb +44 -0
- data/lib/sproutcore/bundle.rb +517 -0
- data/lib/sproutcore/bundle_manifest.rb +397 -0
- data/lib/sproutcore/generator_helper.rb +170 -0
- data/lib/sproutcore/helpers/capture_helper.rb +42 -0
- data/lib/sproutcore/helpers/static_helper.rb +80 -0
- data/lib/sproutcore/helpers/tag_helper.rb +110 -0
- data/lib/sproutcore/helpers/text_helper.rb +336 -0
- data/lib/sproutcore/helpers.rb +3 -0
- data/lib/sproutcore/jsdoc.rb +40 -0
- data/lib/sproutcore/jsmin.rb +247 -0
- data/lib/sproutcore/library.rb +258 -0
- data/lib/sproutcore/merb/bundle_controller.rb +179 -0
- data/lib/sproutcore/merb/router.rb +43 -0
- data/lib/sproutcore/merb.rb +27 -0
- data/lib/sproutcore/version.rb +9 -0
- data/lib/sproutcore/view_helpers/button_views.rb +302 -0
- data/lib/sproutcore/view_helpers/core_views.rb +284 -0
- data/lib/sproutcore/view_helpers/form_views.rb +258 -0
- data/lib/sproutcore/view_helpers/menu_views.rb +94 -0
- data/lib/sproutcore/view_helpers.rb +628 -0
- data/lib/sproutcore.rb +30 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/sproutcore_spec.rb +11 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- metadata +365 -0
@@ -0,0 +1,397 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module SproutCore
|
4
|
+
|
5
|
+
# A Bundle Manifest describes all of the resources in a bundle, including mapping their
|
6
|
+
# source paths, destination paths, and urls.
|
7
|
+
#
|
8
|
+
# A Bundle will create a manifest for every language you request from it. If you invoke
|
9
|
+
# reload! on the bundle, it will dispose of its manifests and rebuild them.
|
10
|
+
#
|
11
|
+
class BundleManifest
|
12
|
+
|
13
|
+
CACHED_TYPES = [:javascript, :stylesheet, :fixture, :test]
|
14
|
+
SYMLINKED_TYPES = [:resource]
|
15
|
+
|
16
|
+
attr_reader :bundle, :language
|
17
|
+
|
18
|
+
def initialize(bundle, language)
|
19
|
+
@bundle = bundle
|
20
|
+
@language = language
|
21
|
+
@entries_by_type = {} # entries by type
|
22
|
+
@entries_by_filename = {} # entries by files
|
23
|
+
build!
|
24
|
+
end
|
25
|
+
|
26
|
+
# ==== Returns
|
27
|
+
# All entries as an array
|
28
|
+
#
|
29
|
+
def entries; @entries_by_filename.values; end
|
30
|
+
|
31
|
+
# ==== Returns
|
32
|
+
# All of the entries matching the specified type
|
33
|
+
#
|
34
|
+
def entries_for(resource_type)
|
35
|
+
@entries_by_type[resource_type] || []
|
36
|
+
end
|
37
|
+
|
38
|
+
# ==== Returns
|
39
|
+
# Entry for the resource with the specified name
|
40
|
+
#
|
41
|
+
def entry_for(resource_name)
|
42
|
+
@entries_by_filename[resource_name] || nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_a; @entries_by_filename.values.map { |x| x.to_hash }; end
|
46
|
+
def to_hash; @entries_by_type; end
|
47
|
+
def to_s; @entries_by_filename.to_yaml; end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
# Builds a manifest for the bundle and the specified language
|
52
|
+
def build!
|
53
|
+
|
54
|
+
# STEP 1: Catalog all of the files in the project, including the target language
|
55
|
+
# and the default language. This will filter out resources not used in this language.
|
56
|
+
entries = catalog_entries
|
57
|
+
|
58
|
+
# STEP 2: Combine the HTML file paths into a single entry, unless this is a framework
|
59
|
+
if (working = entries[:html]) && working.size > 0
|
60
|
+
if bundle.can_have_html?
|
61
|
+
working << build_entry_for('index.html', :html, working)
|
62
|
+
else
|
63
|
+
working.each { |x| x.hidden = true }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# STEP 3: If in development build mode:
|
68
|
+
if bundle.build_mode == :development
|
69
|
+
|
70
|
+
# a. Merge fixture types into JS types & tests
|
71
|
+
unless entries[:fixture].nil?
|
72
|
+
entries[:javascript] = (entries[:javascript] || []) + entries[:fixture]
|
73
|
+
end
|
74
|
+
|
75
|
+
# b. Rewrite all of the JS & CSS file paths and URLs to point to cached versions
|
76
|
+
# (Cached versions are written to _cache/filename-ctime.ext)
|
77
|
+
(entries[:javascript] ||= []).each do | entry |
|
78
|
+
setup_timestamp_token(entry)
|
79
|
+
end
|
80
|
+
|
81
|
+
(entries[:stylesheet] ||= []).each do | entry |
|
82
|
+
setup_timestamp_token(entry)
|
83
|
+
end
|
84
|
+
|
85
|
+
# c. Rewrite the URLs for all other resources to go through the _src symlink
|
86
|
+
##-----> Already done build_entry_for()
|
87
|
+
|
88
|
+
|
89
|
+
# STEP 4: If in production mode, remove extra assets that should never be built
|
90
|
+
else
|
91
|
+
|
92
|
+
# c. Remove the entries for anything that is not JS, CSS, HTML or Resource
|
93
|
+
entries.delete(:fixture)
|
94
|
+
entries.delete(:test)
|
95
|
+
end
|
96
|
+
|
97
|
+
# STEP 5: Add entry for javascript.js & stylesheet.js. If in production mode, set
|
98
|
+
# these to visible and hide the composite. If in dev mode, do the opposite.
|
99
|
+
|
100
|
+
hide_composite = (bundle.build_mode != :development)
|
101
|
+
|
102
|
+
# a. Combine the JS file paths into a single entry for the javascript.js
|
103
|
+
if (working = entries[:javascript]) && working.size>0
|
104
|
+
entry = build_entry_for('javascript.js', :javascript, working, hide_composite)
|
105
|
+
entry.hidden = true unless hide_composite
|
106
|
+
working << entry
|
107
|
+
end
|
108
|
+
|
109
|
+
# b. Combine the CSS file paths into a single entry for the stylesheet.css
|
110
|
+
if (working = entries[:stylesheet]) && working.size>0
|
111
|
+
entry = build_entry_for('stylesheet.css', :stylesheet, working, hide_composite)
|
112
|
+
entry.hidden = true unless hide_composite
|
113
|
+
working << entry
|
114
|
+
end
|
115
|
+
|
116
|
+
# Save entries into hashes
|
117
|
+
@entries_by_type = entries
|
118
|
+
@entries_by_filenames = {}
|
119
|
+
entries.values.flatten.each { |entry| @entries_by_filename[entry.filename] = entry }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Build a catalog of entries for this manifest. This will simply filter out the files
|
123
|
+
# that don't actually belong in the current language
|
124
|
+
def catalog_entries
|
125
|
+
|
126
|
+
# Entries arranged by resource filename
|
127
|
+
entries = {}
|
128
|
+
default_lproj_entries = {}
|
129
|
+
target_lproj_entries = {}
|
130
|
+
|
131
|
+
# Get the name of the lproj dirs for the default and current languages
|
132
|
+
default_lproj = bundle.lproj_for(bundle.preferred_language)
|
133
|
+
target_lproj = bundle.lproj_for(language)
|
134
|
+
|
135
|
+
# Any files living in the two lproj dirs will be shunted off into these arrays
|
136
|
+
# and processed later to make sure we process them in the right order
|
137
|
+
default_lproj_files = []
|
138
|
+
target_lproj_files = []
|
139
|
+
|
140
|
+
# Now, glob all the files and sort through them
|
141
|
+
old_wd = Dir.getwd; Dir.chdir(bundle.source_root)
|
142
|
+
Dir.glob(File.join('**','*')).each do | src_path |
|
143
|
+
|
144
|
+
# Get source type. Skip any without a useful type
|
145
|
+
next if (src_type = type_of(src_path)) == :skip
|
146
|
+
|
147
|
+
# Get current lproj (if there is one). Skip if not default or current
|
148
|
+
if current_lproj = src_path.match(/^([^\/]+\.lproj)\//).to_a[1]
|
149
|
+
next if (current_lproj != default_lproj) && (current_lproj != target_lproj)
|
150
|
+
end
|
151
|
+
|
152
|
+
# OK, pass all of our validations. Go ahead and build an entry for this
|
153
|
+
# Add entry to list of entries for appropriate lproj if localized
|
154
|
+
entry = build_entry_for(src_path, src_type)
|
155
|
+
case current_lproj
|
156
|
+
when default_lproj
|
157
|
+
default_lproj_entries[entry.filename] = entry
|
158
|
+
when target_lproj
|
159
|
+
target_lproj_entries[entry.filename] = entry
|
160
|
+
else
|
161
|
+
entries[entry.filename] = entry
|
162
|
+
end
|
163
|
+
end
|
164
|
+
Dir.chdir(old_wd) # restore wd
|
165
|
+
|
166
|
+
# Now, new in default and target lproj entries. This will overwrite entries that exist
|
167
|
+
# in both places.
|
168
|
+
entries.merge!(default_lproj_entries)
|
169
|
+
entries.merge!(target_lproj_entries)
|
170
|
+
|
171
|
+
# Finally, entries will need to be grouped by type to allow further processing.
|
172
|
+
ret = {}
|
173
|
+
entries.values.each { |entry| (ret[entry.type] ||= []) << entry }
|
174
|
+
return ret
|
175
|
+
end
|
176
|
+
|
177
|
+
# Determines the type for this manifest entry. Should be one of:
|
178
|
+
#
|
179
|
+
# stylesheet:: a CSS file
|
180
|
+
# javascript:: a JavaScript file
|
181
|
+
# html:: an HTML file
|
182
|
+
#
|
183
|
+
# test:: a test file (inside of /tests)
|
184
|
+
# fixture:: a fixture file (inside of /fixtures)
|
185
|
+
#
|
186
|
+
# resource:: any other file inside an lproj dir
|
187
|
+
# skip:: any other file outside of an lproj dir directories
|
188
|
+
#
|
189
|
+
# If you need to handle additional types of resources in the future, this is the place to
|
190
|
+
# put it.
|
191
|
+
#
|
192
|
+
# ==== Params
|
193
|
+
#
|
194
|
+
# src_path:: The source path, relative to source_root.
|
195
|
+
#
|
196
|
+
def type_of(src_path)
|
197
|
+
return :skip if File.directory?(src_path)
|
198
|
+
case src_path
|
199
|
+
when /^tests\/.+/
|
200
|
+
:test
|
201
|
+
when /^fixtures\/.+/
|
202
|
+
:fixture
|
203
|
+
when /.html$/
|
204
|
+
:html
|
205
|
+
when /.rhtml$/
|
206
|
+
:html
|
207
|
+
when /.html.erb$/
|
208
|
+
:html
|
209
|
+
when /.css$/
|
210
|
+
:stylesheet
|
211
|
+
when /.js$/
|
212
|
+
:javascript
|
213
|
+
when /\.lproj\/.+/
|
214
|
+
:resource
|
215
|
+
else
|
216
|
+
:skip
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Build an entry for the resource at the named src_path (relative to the source_root)
|
221
|
+
# This should assume we are in going to simply build each resource into the build root
|
222
|
+
# without combining files, but not using our _src symlink magic.
|
223
|
+
def build_entry_for(src_path, src_type, composite=nil, hide_composite = true)
|
224
|
+
ret = ManifestEntry.new
|
225
|
+
ret.ext = File.extname(src_path)[1..-1] || '' # easy stuff
|
226
|
+
ret.type = src_type
|
227
|
+
ret.original_path = src_path
|
228
|
+
ret.hidden = false
|
229
|
+
ret.language = language
|
230
|
+
|
231
|
+
# the filename is the src_path less any lproj in the front
|
232
|
+
ret.filename = src_path.gsub(/^[^\/]+.lproj\//,'')
|
233
|
+
|
234
|
+
# the source path is just the combine source root + the path
|
235
|
+
ret.source_path = (composite.nil?) ? File.join(bundle.source_root, src_path) : nil
|
236
|
+
|
237
|
+
# set the composite property. The passed in array should contain other entries
|
238
|
+
# if hide_composite is true, then hide the composite items as well
|
239
|
+
unless composite.nil? || composite.size == 0
|
240
|
+
composite.each { |x| x.hidden = true } if hide_composite
|
241
|
+
ret.composite = composite.map { |x| x.filename }
|
242
|
+
end
|
243
|
+
|
244
|
+
# The build path is the build_root + the filename
|
245
|
+
# The URL is the url root + the language code + filename
|
246
|
+
# also add in _cache or _sym in certain cases. This is just more efficient than doing
|
247
|
+
# it later.
|
248
|
+
url_root = (src_path == 'index.html') ? bundle.index_root : bundle.url_root
|
249
|
+
cache_link = nil
|
250
|
+
use_symlink =false
|
251
|
+
|
252
|
+
# Note: you can only access real resources via the cache. If the entry is a composite
|
253
|
+
# then do not go through cache.
|
254
|
+
if (bundle.build_mode == :development) && composite.nil?
|
255
|
+
cache_link = '_cache' if CACHED_TYPES.include?(src_type)
|
256
|
+
use_symlink = true if SYMLINKED_TYPES.include?(src_type)
|
257
|
+
end
|
258
|
+
|
259
|
+
ret.use_symlink = use_symlink
|
260
|
+
if use_symlink
|
261
|
+
ret.build_path = File.join(bundle.build_root, '_src', src_path)
|
262
|
+
ret.url = [url_root, '_src', src_path].join('/')
|
263
|
+
else
|
264
|
+
ret.build_path = File.join(*[bundle.build_root, language.to_s, cache_link, ret.filename].compact)
|
265
|
+
ret.url = [url_root, language.to_s, cache_link, ret.filename].compact.join('/')
|
266
|
+
end
|
267
|
+
|
268
|
+
# Done.
|
269
|
+
return ret
|
270
|
+
end
|
271
|
+
|
272
|
+
# Lookup the timestamp on the source path and interpolate that into the filename URL.
|
273
|
+
# also insert the _cache element.
|
274
|
+
def setup_timestamp_token(entry)
|
275
|
+
timestamp = entry.timestamp
|
276
|
+
extname = File.extname(entry.url)
|
277
|
+
entry.url = entry.url.gsub(/#{extname}$/,"-#{timestamp}#{extname}") # add timestamp
|
278
|
+
|
279
|
+
extname = File.extname(entry.build_path)
|
280
|
+
entry.build_path = entry.build_path.gsub(/#{extname}$/,"-#{timestamp}#{extname}") # add timestamp
|
281
|
+
|
282
|
+
puts "\n\n*setup_timestamp_token(#{entry.url} - #{entry.timestamp})" if /docs-/ =~ entry.url
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# describes a single entry in the Manifest:
|
287
|
+
#
|
288
|
+
# filename:: path relative to the built language (e.g. sproutcore/en) less file extension
|
289
|
+
# ext:: the file extension
|
290
|
+
# source_path:: absolute paths into source that will comprise this resource
|
291
|
+
# url:: the url that should be used to reference this resource
|
292
|
+
# build_path:: absolute path to the compiled resource
|
293
|
+
# type:: the top-level category
|
294
|
+
# original_path:: save the original path used to build this entry
|
295
|
+
# hidden:: if true, this entry is needed internally, but otherwise should not be used
|
296
|
+
# use_symlink:: if true, then this entry should be handled via the build symlink
|
297
|
+
# language:: the language in use when this entry was created
|
298
|
+
# composite:: If set, this will contain the filenames of other resources that should be
|
299
|
+
# combined to form this resource.
|
300
|
+
#
|
301
|
+
class ManifestEntry < Struct.new(:filename, :ext, :source_path, :url, :build_path, :type, :original_path, :hidden, :use_symlink, :language, :composite)
|
302
|
+
def to_hash
|
303
|
+
ret = {}
|
304
|
+
self.members.zip(self.values).each { |p| ret[p[0]] = p[1] }
|
305
|
+
ret.symbolize_keys
|
306
|
+
end
|
307
|
+
|
308
|
+
def hidden?; !!hidden; end
|
309
|
+
def use_symlink?; !!use_symlink; end
|
310
|
+
def composite?; !!composite; end
|
311
|
+
|
312
|
+
# Returns true if this entry can be cached even in development mode. Composite resources
|
313
|
+
# and tests need to be regenerated whenever you get this.
|
314
|
+
def cacheable?
|
315
|
+
!composite? && (type != :test)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Returns the mtime of the source_path. If this entry is a composite or if the source
|
319
|
+
# file does not exist, returns nil
|
320
|
+
def source_path_mtime
|
321
|
+
(composite? || !File.exists?(source_path)) ? nil : File.mtime(source_path)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Returns a timestamp based on the source_path_mtime. If source_path_mtime is nil, always
|
325
|
+
# returns a new timestamp
|
326
|
+
def timestamp
|
327
|
+
(source_path_mtime || Time.now).to_i.to_s
|
328
|
+
end
|
329
|
+
|
330
|
+
# Returns the content type for this entry. Based on a set of MIME_TYPES borrowed from Rack
|
331
|
+
def content_type
|
332
|
+
MIME_TYPES[File.extname(build_path)[1..-1]] || 'text/plain'
|
333
|
+
end
|
334
|
+
|
335
|
+
# :stopdoc:
|
336
|
+
# From WEBrick.
|
337
|
+
MIME_TYPES = {
|
338
|
+
"ai" => "application/postscript",
|
339
|
+
"asc" => "text/plain",
|
340
|
+
"avi" => "video/x-msvideo",
|
341
|
+
"bin" => "application/octet-stream",
|
342
|
+
"bmp" => "image/bmp",
|
343
|
+
"class" => "application/octet-stream",
|
344
|
+
"cer" => "application/pkix-cert",
|
345
|
+
"crl" => "application/pkix-crl",
|
346
|
+
"crt" => "application/x-x509-ca-cert",
|
347
|
+
#"crl" => "application/x-pkcs7-crl",
|
348
|
+
"css" => "text/css",
|
349
|
+
"dms" => "application/octet-stream",
|
350
|
+
"doc" => "application/msword",
|
351
|
+
"dvi" => "application/x-dvi",
|
352
|
+
"eps" => "application/postscript",
|
353
|
+
"etx" => "text/x-setext",
|
354
|
+
"exe" => "application/octet-stream",
|
355
|
+
"gif" => "image/gif",
|
356
|
+
"htm" => "text/html",
|
357
|
+
"html" => "text/html",
|
358
|
+
"rhtml" => "text/html",
|
359
|
+
"jpe" => "image/jpeg",
|
360
|
+
"jpeg" => "image/jpeg",
|
361
|
+
"jpg" => "image/jpeg",
|
362
|
+
"lha" => "application/octet-stream",
|
363
|
+
"lzh" => "application/octet-stream",
|
364
|
+
"mov" => "video/quicktime",
|
365
|
+
"mpe" => "video/mpeg",
|
366
|
+
"mpeg" => "video/mpeg",
|
367
|
+
"mpg" => "video/mpeg",
|
368
|
+
"pbm" => "image/x-portable-bitmap",
|
369
|
+
"pdf" => "application/pdf",
|
370
|
+
"pgm" => "image/x-portable-graymap",
|
371
|
+
"png" => "image/png",
|
372
|
+
"pnm" => "image/x-portable-anymap",
|
373
|
+
"ppm" => "image/x-portable-pixmap",
|
374
|
+
"ppt" => "application/vnd.ms-powerpoint",
|
375
|
+
"ps" => "application/postscript",
|
376
|
+
"qt" => "video/quicktime",
|
377
|
+
"ras" => "image/x-cmu-raster",
|
378
|
+
"rb" => "text/plain",
|
379
|
+
"rd" => "text/plain",
|
380
|
+
"rtf" => "application/rtf",
|
381
|
+
"sgm" => "text/sgml",
|
382
|
+
"sgml" => "text/sgml",
|
383
|
+
"tif" => "image/tiff",
|
384
|
+
"tiff" => "image/tiff",
|
385
|
+
"txt" => "text/plain",
|
386
|
+
"xbm" => "image/x-xbitmap",
|
387
|
+
"xls" => "application/vnd.ms-excel",
|
388
|
+
"xml" => "text/xml",
|
389
|
+
"xpm" => "image/x-xpixmap",
|
390
|
+
"xwd" => "image/x-xwindowdump",
|
391
|
+
"zip" => "application/zip",
|
392
|
+
}
|
393
|
+
# :startdoc:
|
394
|
+
|
395
|
+
end
|
396
|
+
|
397
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
|
2
|
+
module SproutCore
|
3
|
+
|
4
|
+
# You can use these methods basically copy the contents of your templates
|
5
|
+
# directory into a target location.
|
6
|
+
module GeneratorHelper
|
7
|
+
|
8
|
+
def apply_template(m, root_dir)
|
9
|
+
m.directory root_dir
|
10
|
+
|
11
|
+
template_directories.each do |d|
|
12
|
+
m.directory File.join(root_dir,d)
|
13
|
+
end
|
14
|
+
|
15
|
+
template_files.each do |f|
|
16
|
+
f_out = File.join(root_dir,f)
|
17
|
+
if f =~ /\.rhtml$/
|
18
|
+
m.file f, f_out
|
19
|
+
else
|
20
|
+
m.template f, f_out
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def template_directories
|
26
|
+
template_files(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def template_files(directories=false, cur_dir=nil,cur_base=nil)
|
30
|
+
if cur_dir.nil?
|
31
|
+
cur_dir = File.join(spec.path,'templates')
|
32
|
+
cur_base = ''
|
33
|
+
end
|
34
|
+
|
35
|
+
ret = []
|
36
|
+
Dir.foreach(cur_dir) do |x|
|
37
|
+
next if (x == '.' || x == '..' || x == '.svn')
|
38
|
+
base = cur_base + x
|
39
|
+
dir = File.join(cur_dir,x)
|
40
|
+
|
41
|
+
if File.directory?(dir)
|
42
|
+
ret << (cur_base + x) if directories
|
43
|
+
ret += template_files(directories, dir, base + "/")
|
44
|
+
else
|
45
|
+
ret << (cur_base + x) if !directories
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
return ret
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_client_directories(m, path)
|
53
|
+
parts = File.dirname(path).split('/')
|
54
|
+
cpath = []
|
55
|
+
parts.each do |p|
|
56
|
+
cpath << p
|
57
|
+
m.directory File.join(cpath)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convert the Ruby version of the class name to a JavaScript version.
|
62
|
+
def client_class_name
|
63
|
+
class_name.gsub('::','.')
|
64
|
+
end
|
65
|
+
|
66
|
+
def client_namespace
|
67
|
+
parts = client_class_name.split('.')
|
68
|
+
parts.pop
|
69
|
+
return parts * '.'
|
70
|
+
end
|
71
|
+
|
72
|
+
def controller_class_name
|
73
|
+
ret = client_class_name
|
74
|
+
ret += 'Controller' unless ret =~ /Controller$/
|
75
|
+
ret
|
76
|
+
end
|
77
|
+
|
78
|
+
def controller_instance_name
|
79
|
+
ret = controller_class_name.split('.')
|
80
|
+
ret[ret.size-1] = ret.last.underscore.camelize(:lower)
|
81
|
+
return ret * "."
|
82
|
+
end
|
83
|
+
|
84
|
+
def view_class_name
|
85
|
+
ret = client_class_name
|
86
|
+
ret += 'View' unless ret =~ /View$/
|
87
|
+
ret
|
88
|
+
end
|
89
|
+
|
90
|
+
# This will convert the file_name provided by Rails to one suitable for
|
91
|
+
# the client. i.e. calendar/event => calendar/sub_dir/event
|
92
|
+
def client_file_path(sub_dir, ext=nil, to_strip = nil, fp = nil)
|
93
|
+
parts = (fp.nil? ? file_path : fp).split('/')
|
94
|
+
|
95
|
+
# Determine the root dir. Search clients then frameworks.
|
96
|
+
loc = @client_location
|
97
|
+
|
98
|
+
# check for clients
|
99
|
+
if loc.nil?
|
100
|
+
client_path = File.join(destination_root, "clients")
|
101
|
+
app_name = parts.first
|
102
|
+
loc = "clients" if File.exists?(client_path) && Dir.new(client_path).include?(app_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
# check for frameworks
|
106
|
+
if loc.nil?
|
107
|
+
client_path = File.join(destination_root, "frameworks")
|
108
|
+
app_name = parts.first
|
109
|
+
loc = "frameworks" if File.exists?(client_path) && Dir.new(client_path).include?(app_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
loc = "clients" if loc.nil? # fallback
|
113
|
+
|
114
|
+
path = parts.insert(parts.size-1,sub_dir).unshift(loc) * '/'
|
115
|
+
|
116
|
+
# We want to stop the final part of the name for controllers and views.
|
117
|
+
if to_strip && (path =~ /_#{to_strip}$/)
|
118
|
+
path = path.slice(0,path.size - (to_strip.size + 1))
|
119
|
+
end
|
120
|
+
|
121
|
+
[path,ext].compact * '.'
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns the base class name, which is the first argument or a default.
|
125
|
+
def base_class_name(default_base_class_name = 'SC.Object')
|
126
|
+
@args.first || default_base_class_name
|
127
|
+
end
|
128
|
+
|
129
|
+
###################
|
130
|
+
# Borrowed from Rails NamedBase
|
131
|
+
|
132
|
+
attr_reader :name, :class_name, :singular_name, :plural_name
|
133
|
+
attr_reader :class_path, :file_path, :class_nesting, :class_nesting_depth
|
134
|
+
alias_method :file_name, :singular_name
|
135
|
+
|
136
|
+
def assign_names!(name)
|
137
|
+
@name = name
|
138
|
+
base_name, @class_path, @file_path, @class_nesting, @class_nesting_depth = extract_modules(@name)
|
139
|
+
@class_name_without_nesting, @singular_name, @plural_name = inflect_names(base_name)
|
140
|
+
if @class_nesting.empty?
|
141
|
+
@class_name = @class_name_without_nesting
|
142
|
+
else
|
143
|
+
@class_name = "#{@class_nesting}::#{@class_name_without_nesting}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Extract modules from filesystem-style or ruby-style path:
|
148
|
+
# good/fun/stuff
|
149
|
+
# Good::Fun::Stuff
|
150
|
+
# produce the same results.
|
151
|
+
def extract_modules(name)
|
152
|
+
modules = name.include?('/') ? name.split('/') : name.split('::')
|
153
|
+
name = modules.pop
|
154
|
+
path = modules.map { |m| m.underscore }
|
155
|
+
file_path = (path + [name.underscore]).join('/')
|
156
|
+
nesting = modules.map { |m| m.camelize }.join('::')
|
157
|
+
[name, path, file_path, nesting, modules.size]
|
158
|
+
end
|
159
|
+
|
160
|
+
def inflect_names(name)
|
161
|
+
camel = name.camelize
|
162
|
+
under = camel.underscore
|
163
|
+
plural = under.pluralize
|
164
|
+
[camel, under, plural]
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
RubiGen::Base.send(:include,SproutCore::GeneratorHelper)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SproutCore
|
2
|
+
|
3
|
+
module Helpers
|
4
|
+
|
5
|
+
module CaptureHelper
|
6
|
+
|
7
|
+
# Captures the resulting value of the block and returns it
|
8
|
+
def capture(*args, &block)
|
9
|
+
begin
|
10
|
+
buffer = eval('_buf', block.binding)
|
11
|
+
rescue
|
12
|
+
buffer = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
if buffer.nil?
|
16
|
+
block.call(*args).to_s
|
17
|
+
else
|
18
|
+
pos = buffer.length
|
19
|
+
block.call(*args)
|
20
|
+
|
21
|
+
# get emitted data
|
22
|
+
data = buffer[pos..-1]
|
23
|
+
|
24
|
+
# remove from buffer
|
25
|
+
buffer[pos..-1] = ''
|
26
|
+
|
27
|
+
data
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# executes the passed block, placing the resulting content into a variable called
|
32
|
+
# @content_for_area_name. You can insert this content later by including this
|
33
|
+
# variable or by calling yield(:area_name)
|
34
|
+
#
|
35
|
+
def content_for(name, &block)
|
36
|
+
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# The helper methods here are used to build the main index template file for
|
2
|
+
# a SproutCore application. See the commented index.rhtml in the plugin for
|
3
|
+
# example usage.
|
4
|
+
#
|
5
|
+
module SproutCore
|
6
|
+
module Helpers
|
7
|
+
|
8
|
+
module StaticHelper
|
9
|
+
|
10
|
+
# This method will return the HTML to link to all the stylesheets required
|
11
|
+
# by the named bundle. If you pass no options, the current client will be
|
12
|
+
# used.
|
13
|
+
#
|
14
|
+
# bundle_name = the name of the bundle to render or nil to use the current
|
15
|
+
# :language => the language to render. defaults to current language
|
16
|
+
#
|
17
|
+
def stylesheets_for_client(bundle_name = nil, opts = {})
|
18
|
+
|
19
|
+
opts[:language] ||= language
|
20
|
+
|
21
|
+
# Get bundle
|
22
|
+
cur_bundle = bundle_name.nil? ? bundle : library.bundle_for(bundle_name)
|
23
|
+
|
24
|
+
# Convert to a list of required bundles
|
25
|
+
all_bundles = cur_bundle.all_required_bundles
|
26
|
+
|
27
|
+
# For each bundle, get the ordered list of stylsheet urls
|
28
|
+
urls = []
|
29
|
+
all_bundles.each do |b|
|
30
|
+
urls += b.sorted_stylesheet_entries(opts).map { |x| x.url }
|
31
|
+
urls += (b.stylesheet_libs || [])
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert to HTML and return
|
35
|
+
urls = urls.map do |url|
|
36
|
+
%( <link href="#{url}" rel="stylesheet" type="text/css" />)
|
37
|
+
end
|
38
|
+
urls.join("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
# This method will return the HTML to link to all the javascripts required
|
42
|
+
# by the client. If you pass no options, the current client will be used.
|
43
|
+
#
|
44
|
+
# client_name = the name of the client to render or nil to use the current
|
45
|
+
# :language => the language to render. defaults to @language.
|
46
|
+
#
|
47
|
+
def javascripts_for_client(bundle_name = nil, opts = {})
|
48
|
+
|
49
|
+
opts[:language] ||= language
|
50
|
+
|
51
|
+
# Get bundle
|
52
|
+
cur_bundle = bundle_name.nil? ? bundle : library.bundle_for(bundle_name)
|
53
|
+
|
54
|
+
# Convert to a list of required bundles
|
55
|
+
all_bundles = cur_bundle.all_required_bundles
|
56
|
+
|
57
|
+
# For each bundle, get the ordered list of stylsheet urls
|
58
|
+
urls = []
|
59
|
+
all_bundles.each do |b|
|
60
|
+
urls += b.sorted_javascript_entries(opts).map { |x| x.url }
|
61
|
+
urls += (b.javascript_libs || [])
|
62
|
+
end
|
63
|
+
|
64
|
+
# Convert to HTML and return
|
65
|
+
urls = urls.map do |url|
|
66
|
+
%( <script type="text/javascript" src="#{url}"></script>)
|
67
|
+
end
|
68
|
+
urls.join("\n")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the URL for the named resource
|
72
|
+
def static_url(resource_name, opts = {})
|
73
|
+
opts[:language] ||= language
|
74
|
+
entry = bundle.find_resource_entry(resource_name, opts)
|
75
|
+
entry.nil? ? '' : entry.url
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|