ee_jammit 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,97 @@
1
+ require 'action_controller'
2
+
3
+ module Jammit
4
+
5
+ # The JammitController is added to your Rails application when the Gem is
6
+ # loaded. It takes responsibility for /assets, and dynamically packages any
7
+ # missing or uncached asset packages.
8
+ class Controller < ActionController::Base
9
+
10
+ VALID_FORMATS = [:css, :js]
11
+
12
+ SUFFIX_STRIPPER = /-(datauri|mhtml)\Z/
13
+
14
+ NOT_FOUND_PATH = "#{Jammit.public_root}/404.html"
15
+
16
+ # The "package" action receives all requests for asset packages that haven't
17
+ # yet been cached. The package will be built, cached, and gzipped.
18
+ def package
19
+ parse_request
20
+ template_ext = Jammit.template_extension.to_sym
21
+ case @extension
22
+ when :js
23
+ render :js => (@contents = Jammit.packager.pack_javascripts(@package))
24
+ when template_ext
25
+ render :js => (@contents = Jammit.packager.pack_templates(@package))
26
+ when :css
27
+ render :text => generate_stylesheets, :content_type => 'text/css'
28
+ end
29
+ cache_package if perform_caching && (@extension != template_ext)
30
+ rescue Jammit::PackageNotFound
31
+ package_not_found
32
+ end
33
+
34
+
35
+ private
36
+
37
+ # Tells the Jammit::Packager to cache and gzip an asset package. We can't
38
+ # just use the built-in "cache_page" because we need to ensure that
39
+ # the timestamp that ends up in the MHTML is also on the cached file.
40
+ def cache_package
41
+ dir = File.join(page_cache_directory, Jammit.package_path)
42
+ Jammit.packager.cache(@package, @extension, @contents, dir, @variant, @mtime)
43
+ end
44
+
45
+ # Generate the complete, timestamped, MHTML url -- if we're rendering a
46
+ # dynamic MHTML package, we'll need to put one URL in the response, and a
47
+ # different one into the cached package.
48
+ def prefix_url(path)
49
+ host = request.port == 80 ? request.host : request.host_with_port
50
+ "#{request.protocol}#{host}#{path}"
51
+ end
52
+
53
+ # If we're generating MHTML/CSS, return a stylesheet with the absolute
54
+ # request URL to the client, and cache a version with the timestamped cache
55
+ # URL swapped in.
56
+ def generate_stylesheets
57
+ return @contents = Jammit.packager.pack_stylesheets(@package, @variant) unless @variant == :mhtml
58
+ @mtime = Time.now
59
+ request_url = prefix_url(request.fullpath)
60
+ cached_url = prefix_url(Jammit.asset_url(@package, @extension, @variant, @mtime))
61
+ css = Jammit.packager.pack_stylesheets(@package, @variant, request_url)
62
+ @contents = css.gsub(request_url, cached_url) if perform_caching
63
+ css
64
+ end
65
+
66
+ # Extracts the package name, extension (:css, :js), and variant (:datauri,
67
+ # :mhtml) from the incoming URL.
68
+ def parse_request
69
+ pack = params[:package]
70
+ @extension = params[:extension].to_sym
71
+ raise PackageNotFound unless (VALID_FORMATS + [Jammit.template_extension.to_sym]).include?(@extension)
72
+ if Jammit.embed_assets
73
+ suffix_match = pack.match(SUFFIX_STRIPPER)
74
+ @variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
75
+ pack.sub!(SUFFIX_STRIPPER, '')
76
+ end
77
+ @package = pack.to_sym
78
+ end
79
+
80
+ # Render the 404 page, if one exists, for any packages that don't.
81
+ def package_not_found
82
+ return render(:file => NOT_FOUND_PATH, :status => 404) if File.exists?(NOT_FOUND_PATH)
83
+ render :text => "<h1>404: \"#{@package}\" asset package not found.</h1>", :status => 404
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ # Make the Jammit::Controller available to Rails as a top-level controller.
91
+ ::JammitController = Jammit::Controller
92
+
93
+ if defined?(Rails) && Rails.env.development?
94
+ ActionController::Base.class_eval do
95
+ append_before_filter { Jammit.reload! }
96
+ end
97
+ end
@@ -0,0 +1,10 @@
1
+ # Wraps CSSMin compressor to use the same API as the rest of
2
+ # Jammit's compressors.
3
+ class Jammit::CssminCompressor
4
+ def initialize(options = {})
5
+ end
6
+
7
+ def compress(css)
8
+ CSSMin.minify(css)
9
+ end
10
+ end
@@ -0,0 +1,61 @@
1
+ # Standard Library Dependencies:
2
+ require 'uri'
3
+ require 'erb'
4
+ require 'zlib'
5
+ require 'yaml'
6
+ require 'base64'
7
+ require 'pathname'
8
+ require 'fileutils'
9
+
10
+ # Try Uglifier.
11
+ begin
12
+ require 'uglifier'
13
+ require 'jammit/uglifier'
14
+ rescue LoadError
15
+ Jammit.javascript_compressors.delete :uglifier
16
+ end
17
+
18
+ # Try YUI
19
+ begin
20
+ require 'yui/compressor'
21
+ rescue LoadError
22
+ Jammit.javascript_compressors.delete :yui
23
+ Jammit.css_compressors.delete :yui
24
+ end
25
+
26
+ # Try Closure.
27
+ begin
28
+ require 'closure-compiler'
29
+ rescue LoadError
30
+ Jammit.javascript_compressors.delete :closure
31
+ end
32
+
33
+ # Try Sass
34
+ begin
35
+ require 'sass'
36
+ require 'jammit/sass_compressor'
37
+ rescue LoadError
38
+ Jammit.css_compressors.delete :sass
39
+ end
40
+
41
+ # Load initial configuration before the rest of Jammit.
42
+ Jammit.load_configuration(Jammit::DEFAULT_CONFIG_PATH, true) if defined?(Rails)
43
+
44
+ # Jammit Core:
45
+ require 'jsmin'
46
+ require 'cssmin'
47
+ require 'jammit/jsmin_compressor'
48
+ require 'jammit/cssmin_compressor'
49
+ require 'jammit/compressor'
50
+ require 'jammit/packager'
51
+ require 'jammit/helper'
52
+ require 'jammit/middleware'
53
+
54
+ # Jammit Rails Integration:
55
+ if defined?(Rails)
56
+ require 'jammit/controller'
57
+ require 'jammit/helper'
58
+ require 'jammit/railtie'
59
+ require 'jammit/routes'
60
+ end
61
+
@@ -0,0 +1,88 @@
1
+ module Jammit
2
+
3
+ # The Jammit::Helper module, which is made available to every view, provides
4
+ # helpers for writing out HTML tags for asset packages. In development you
5
+ # get the ordered list of source files -- in any other environment, a link
6
+ # to the cached packages.
7
+ module Helper
8
+
9
+ DATA_URI_START = "<!--[if (!IE)|(gte IE 8)]><!-->" unless defined?(DATA_URI_START)
10
+ DATA_URI_END = "<!--<![endif]-->" unless defined?(DATA_URI_END)
11
+ MHTML_START = "<!--[if lte IE 7]>" unless defined?(MHTML_START)
12
+ MHTML_END = "<![endif]-->" unless defined?(MHTML_END)
13
+
14
+ # If embed_assets is turned on, writes out links to the Data-URI and MHTML
15
+ # versions of the stylesheet package, otherwise the package is regular
16
+ # compressed CSS, and in development the stylesheet URLs are passed verbatim.
17
+ def include_stylesheets(*packages)
18
+ options = packages.extract_options!
19
+ return html_safe(individual_stylesheets(packages, options)) unless should_package?
20
+ disabled = (options.delete(:embed_assets) == false) || (options.delete(:embed_images) == false)
21
+ return html_safe(packaged_stylesheets(packages, options)) if disabled || !Jammit.embed_assets
22
+ return html_safe(embedded_image_stylesheets(packages, options))
23
+ end
24
+
25
+ # Writes out the URL to the bundled and compressed javascript package,
26
+ # except in development, where it references the individual scripts.
27
+ def include_javascripts(*packages)
28
+ options = packages.extract_options!
29
+ html_safe packages.map {|pack|
30
+ should_package? ? Jammit.asset_url(pack, :js) : Jammit.packager.individual_urls(pack.to_sym, :js)
31
+ }.flatten.map {|pack|
32
+ javascript_include_tag pack, options
33
+ }.join("\n")
34
+ end
35
+
36
+ # Writes out the URL to the concatenated and compiled JST file -- we always
37
+ # have to pre-process it, even in development.
38
+ def include_templates(*packages)
39
+ raise DeprecationError, "Jammit 0.5+ no longer supports separate packages for templates.\nYou can include your JST alongside your JS, and use include_javascripts."
40
+ end
41
+
42
+
43
+ private
44
+
45
+ def should_package?
46
+ Jammit.package_assets && !(Jammit.allow_debugging && params[:debug_assets])
47
+ end
48
+
49
+ def html_safe(string)
50
+ string.respond_to?(:html_safe) ? string.html_safe : string
51
+ end
52
+
53
+ # HTML tags, in order, for all of the individual stylesheets.
54
+ def individual_stylesheets(packages, options)
55
+ tags_with_options(packages, options) {|p| Jammit.packager.individual_urls(p.to_sym, :css) }
56
+ end
57
+
58
+ # HTML tags for the stylesheet packages.
59
+ def packaged_stylesheets(packages, options)
60
+ tags_with_options(packages, options) {|p| Jammit.asset_url(p, :css) }
61
+ end
62
+
63
+ # HTML tags for the 'datauri', and 'mhtml' versions of the packaged
64
+ # stylesheets, using conditional comments to load the correct variant.
65
+ def embedded_image_stylesheets(packages, options)
66
+ datauri_tags = tags_with_options(packages, options) {|p| Jammit.asset_url(p, :css, :datauri) }
67
+ ie_tags = Jammit.mhtml_enabled ?
68
+ tags_with_options(packages, options) {|p| Jammit.asset_url(p, :css, :mhtml) } :
69
+ packaged_stylesheets(packages, options)
70
+ [DATA_URI_START, datauri_tags, DATA_URI_END, MHTML_START, ie_tags, MHTML_END].join("\n")
71
+ end
72
+
73
+ # Generate the stylesheet tags for a batch of packages, with options, by
74
+ # yielding each package to a block.
75
+ def tags_with_options(packages, options)
76
+ packages.dup.map {|package|
77
+ yield package
78
+ }.flatten.map {|package|
79
+ stylesheet_link_tag package, options
80
+ }.join("\n")
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+
87
+ # Include the Jammit asset helpers in all views, a-la ApplicationHelper.
88
+ ::ActionView::Base.send(:include, Jammit::Helper) if defined?(::ActionView)
@@ -0,0 +1,10 @@
1
+ # Wraps JSMin compressor to use the same API as the rest of
2
+ # Jammit's compressors.
3
+ class Jammit::JsminCompressor
4
+ def initialize(options = {})
5
+ end
6
+
7
+ def compress(js)
8
+ JSMin.minify(js)
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ var template = function(str){var fn = new Function('obj', 'var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push(\''+str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g,function(match,code){return "',"+code.replace(/\\'/g, "'")+",'";}).replace(/<%([\s\S]+?)%>/g,function(match,code){return "');"+code.replace(/\\'/g, "'").replace(/[\r\n\t]/g,' ')+"__p.push('";}).replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/\t/g,'\\t')+"');}return __p.join('');");return fn;};
@@ -0,0 +1,115 @@
1
+ module Jammit
2
+
3
+ # Rack Middle that allows Jammit to integrate with any Rack compatible web
4
+ # framework. It takes responsibility for /assets, and dynamically packages
5
+ # any missing or uncached asset packages.
6
+
7
+ class Middleware
8
+
9
+ VALID_FORMATS = [:css, :js]
10
+
11
+ SUFFIX_STRIPPER = /-(datauri|mhtml)\Z/
12
+
13
+ PUBLIC_ROOT = defined?(Jammit.public_root) ? Jammit.public_root : PUBLIC_ROOT
14
+ NOT_FOUND_PATH = "#{PUBLIC_ROOT}/404.html"
15
+
16
+ def initialize(app)
17
+ @app = app
18
+ end
19
+
20
+ def call(env)
21
+ dup._call(env)
22
+ end
23
+
24
+ def _call(env)
25
+ if matches = %r(^/#{Jammit.package_path}/(.*)\.(.*)).match(env['PATH_INFO'])
26
+ package(matches[1].to_s, matches[2] || "none")
27
+ else
28
+ @app.call(env)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # The "package" action receives all requests for asset packages that haven't
35
+ # yet been cached. The package will be built, cached, and gzipped.
36
+ def package(package, extension)
37
+ parse_request(package, extension)
38
+ template_ext = Jammit.template_extension.to_sym
39
+ result = []
40
+
41
+ headers = {
42
+ 'Cache-Control' => "public, max-age=#{(60*60*24*365.25*10).to_i}",
43
+ 'Expires' => (Time.now + 60*60*24*365.25*10).httpdate
44
+ }
45
+
46
+ case @extension
47
+ when :js
48
+ @contents = Jammit.packager.pack_javascripts(@package)
49
+ @contents = @contents.to_js if @contents.respond_to?(:to_js)
50
+ result = [
51
+ 200,
52
+ headers.merge({
53
+ 'Content-Type' => Rack::Mime.mime_type(".js")
54
+ }),
55
+ [@contents]
56
+ ]
57
+ when template_ext
58
+ @contents = Jammit.packager.pack_templates(@package)
59
+ @contents = @contents.to_js if @contents.respond_to?(:to_js)
60
+ [
61
+ 200,
62
+ headers.merge({
63
+ 'Content-Type' => Rack::Mime.mime_type(".js")
64
+ }),
65
+ [@contents]
66
+ ]
67
+ when :css
68
+ [
69
+ 200,
70
+ headers.merge({
71
+ 'Content-Type' => Rack::Mime.mime_type(".css")
72
+ }),
73
+ [generate_stylesheets]
74
+ ]
75
+ end
76
+ rescue Jammit::PackageNotFound
77
+ [404, {'Content-Type' => 'text/plain'}, ['Not found']]
78
+ end
79
+
80
+ # Generate the complete, timestamped, MHTML url -- if we're rendering a
81
+ # dynamic MHTML package, we'll need to put one URL in the response, and a
82
+ # different one into the cached package.
83
+ def prefix_url(path)
84
+ host = request.port == 80 ? request.host : request.host_with_port
85
+ "#{request.protocol}#{host}#{path}"
86
+ end
87
+
88
+ # If we're generating MHTML/CSS, return a stylesheet with the absolute
89
+ # request URL to the client, and cache a version with the timestamped cache
90
+ # URL swapped in.
91
+ def generate_stylesheets
92
+ return @contents = Jammit.packager.pack_stylesheets(@package, @variant) unless @variant == :mhtml
93
+ @mtime = Time.now
94
+ request_url = prefix_url(request.fullpath)
95
+ cached_url = prefix_url(Jammit.asset_url(@package, @extension, @variant, @mtime))
96
+ css = Jammit.packager.pack_stylesheets(@package, @variant, request_url)
97
+ # @contents = css.gsub(request_url, cached_url) if perform_caching
98
+ css
99
+ end
100
+
101
+ # Extracts the package name, extension (:css, :js), and variant (:datauri,
102
+ # :mhtml) from the incoming URL.
103
+ def parse_request(pack, extension)
104
+ @extension = extension.to_sym
105
+ raise PackageNotFound unless (VALID_FORMATS + [Jammit.template_extension.to_sym]).include?(@extension)
106
+ if Jammit.embed_assets
107
+ suffix_match = pack.match(SUFFIX_STRIPPER)
108
+ @variant = Jammit.embed_assets && suffix_match && suffix_match[1].to_sym
109
+ pack.sub!(SUFFIX_STRIPPER, '')
110
+ end
111
+ @package = pack.to_sym
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,179 @@
1
+ module Jammit
2
+
3
+ # The Jammit::Packager resolves the configuration file into lists of real
4
+ # assets that get merged into individual asset packages. Given the compiled
5
+ # contents of an asset package, the Packager knows how to cache that package
6
+ # with the correct timestamps.
7
+ class Packager
8
+
9
+ # Set force to false to allow packages to only be rebuilt when their source
10
+ # files have changed since the last time their package was built.
11
+ attr_accessor :force, :package_names
12
+
13
+ # Creating a new Packager will rebuild the list of assets from the
14
+ # Jammit.configuration. When assets.yml is being changed on the fly,
15
+ # create a new Packager.
16
+ def initialize
17
+ @force = false
18
+ @package_names = nil
19
+ @config = {
20
+ :css => (Jammit.configuration[:stylesheets] || {}),
21
+ :js => (Jammit.configuration[:javascripts] || {})
22
+ }
23
+ @packages = {
24
+ :css => create_packages(@config[:css]),
25
+ :js => create_packages(@config[:js])
26
+ }
27
+ end
28
+
29
+ # Ask the packager to precache all defined assets, along with their gzip'd
30
+ # versions. In order to prebuild the MHTML stylesheets, we need to know the
31
+ # base_url, because IE only supports MHTML with absolute references.
32
+ # Unless forced, will only rebuild assets whose source files have been
33
+ # changed since their last package build.
34
+ def precache_all(output_dir=nil, base_url=nil)
35
+ output_dir ||= File.join(Jammit.public_root, Jammit.package_path)
36
+ cacheable(:js, output_dir).each {|p| cache(p, 'js', pack_javascripts(p), output_dir) }
37
+ cacheable(:css, output_dir).each do |p|
38
+ cache(p, 'css', pack_stylesheets(p), output_dir)
39
+ if Jammit.embed_assets
40
+ cache(p, 'css', pack_stylesheets(p, :datauri), output_dir, :datauri)
41
+ if Jammit.mhtml_enabled
42
+ raise MissingConfiguration, "A --base-url option is required in order to generate MHTML." unless base_url
43
+ mtime = latest_mtime package_for(p, :css)[:paths]
44
+ asset_url = "#{base_url}#{Jammit.asset_url(p, :css, :mhtml, mtime)}"
45
+ cache(p, 'css', pack_stylesheets(p, :mhtml, asset_url), output_dir, :mhtml, mtime)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ # Caches a single prebuilt asset package and gzips it at the highest
52
+ # compression level. Ensures that the modification time of both both
53
+ # variants is identical, for web server caching modules, as well as MHTML.
54
+ def cache(package, extension, contents, output_dir, suffix=nil, mtime=nil)
55
+ FileUtils.mkdir_p(output_dir) unless File.exists?(output_dir)
56
+ raise OutputNotWritable, "Jammit doesn't have permission to write to \"#{output_dir}\"" unless File.writable?(output_dir)
57
+ mtime ||= latest_mtime package_for(package, extension.to_sym)[:paths]
58
+ files = []
59
+ files << file_name = File.join(output_dir, Jammit.filename(package, extension, suffix))
60
+ File.open(file_name, 'wb+') {|f| f.write(contents) }
61
+ if Jammit.gzip_assets
62
+ files << zip_name = "#{file_name}.gz"
63
+ Zlib::GzipWriter.open(zip_name, Zlib::BEST_COMPRESSION) {|f| f.write(contents) }
64
+ end
65
+ File.utime(mtime, mtime, *files)
66
+ end
67
+
68
+ # Get the list of individual assets for a package.
69
+ def individual_urls(package, extension)
70
+ package_for(package, extension)[:urls]
71
+ end
72
+
73
+ def compressor
74
+ @compressor ||= Compressor.new
75
+ end
76
+
77
+ # Return the compressed contents of a stylesheet package.
78
+ def pack_stylesheets(package, variant=nil, asset_url=nil)
79
+ compressor.compress_css(package_for(package, :css)[:paths], variant, asset_url)
80
+ end
81
+
82
+ # Return the compressed contents of a javascript package.
83
+ def pack_javascripts(package)
84
+ compressor.compress_js(package_for(package, :js)[:paths])
85
+ end
86
+
87
+ # Return the compiled contents of a JST package.
88
+ def pack_templates(package)
89
+ compressor.compile_jst(package_for(package, :js)[:paths])
90
+ end
91
+
92
+
93
+ private
94
+
95
+ # Look up a package asset list by name, raising an exception if the
96
+ # package has gone missing.
97
+ def package_for(package, extension)
98
+ pack = @packages[extension] && @packages[extension][package]
99
+ pack || not_found(package, extension)
100
+ end
101
+
102
+ # Absolute globs are absolute -- relative globs are relative to ASSET_ROOT.
103
+ # Print a warning if no files were found that match the glob.
104
+ def glob_files(glob)
105
+ absolute = Pathname.new(glob).absolute?
106
+ paths = Dir[absolute ? glob : File.join(ASSET_ROOT, glob)].sort
107
+ Jammit.warn("No assets match '#{glob}'") if paths.empty?
108
+ paths
109
+ end
110
+
111
+ # In Rails, the difference between a path and an asset URL is "public".
112
+ def path_to_url
113
+ @path_to_url ||= /\A#{Regexp.escape(ASSET_ROOT)}(\/?#{Regexp.escape(Jammit.public_root.sub(ASSET_ROOT, ''))})?/
114
+ end
115
+
116
+ # Get the latest mtime of a list of files (plus the config path).
117
+ def latest_mtime(paths)
118
+ paths += [Jammit.config_path]
119
+ paths.map {|p| File.mtime(p) }.max || Time.now
120
+ end
121
+
122
+ # Return a list of all of the packages that should be cached. If "force" is
123
+ # true, this is all of them -- otherwise only the packages that are missing
124
+ # or whose source files have changed since the last package build.
125
+ def cacheable(extension, output_dir)
126
+ names = @packages[extension].keys
127
+ names = names.select {|n| @package_names.include? n } if @package_names
128
+ config_mtime = File.mtime(Jammit.config_path)
129
+ return names if @force
130
+ return names.select do |name|
131
+ pack = package_for(name, extension)
132
+ cached = [Jammit.filename(name, extension)]
133
+ if extension == :css
134
+ cached.push Jammit.filename(name, extension, :datauri) if Jammit.embed_assets
135
+ cached.push Jammit.filename(name, extension, :mhtml) if Jammit.mhtml_enabled
136
+ end
137
+ cached.map! {|file| File.join(output_dir, file) }
138
+ if cached.any? {|file| !File.exists?(file) }
139
+ true
140
+ else
141
+ since = cached.map {|file| File.mtime(file) }.min
142
+ config_mtime > since || pack[:paths].any? {|src| File.mtime(src) > since }
143
+ end
144
+ end
145
+ end
146
+
147
+ # Compiles the list of assets that goes into each package. Runs an
148
+ # ordered list of Dir.globs, taking the merged unique result.
149
+ # If there are JST files in this package we need to add an extra
150
+ # path for when package_assets is off (e.g. in a dev environment).
151
+ # This package (e.g. /assets/package-name.jst) will never exist as
152
+ # an actual file but will be dynamically generated by Jammit on
153
+ # every request.
154
+ def create_packages(config)
155
+ packages = {}
156
+ return packages if !config
157
+ config.each do |name, globs|
158
+ globs ||= []
159
+ packages[name] = {}
160
+ paths = globs.flatten.uniq.map {|glob| glob_files(glob) }.flatten.uniq
161
+ packages[name][:paths] = paths
162
+ if !paths.grep(Jammit.template_extension_matcher).empty?
163
+ packages[name][:urls] = paths.grep(JS_EXTENSION).map {|path| path.sub(path_to_url, '') }
164
+ packages[name][:urls] += [Jammit.asset_url(name, Jammit.template_extension)]
165
+ else
166
+ packages[name][:urls] = paths.map {|path| path.sub(path_to_url, '') }
167
+ end
168
+ end
169
+ packages
170
+ end
171
+
172
+ # Raise a PackageNotFound exception for missing packages...
173
+ def not_found(package, extension)
174
+ raise PackageNotFound, "assets.yml does not contain a \"#{package}\" #{extension.to_s.upcase} package"
175
+ end
176
+
177
+ end
178
+
179
+ end