joosy 0.1.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +159 -0
  4. data/Guardfile +30 -0
  5. data/MIT-LICENSE +21 -0
  6. data/README.rdoc +3 -0
  7. data/Rakefile +14 -0
  8. data/app/assets/javascripts/joosy.js.coffee +7 -0
  9. data/app/assets/javascripts/joosy/core/application.js.coffee +28 -0
  10. data/app/assets/javascripts/joosy/core/form.js.coffee +87 -0
  11. data/app/assets/javascripts/joosy/core/helpers.js.coffee +6 -0
  12. data/app/assets/javascripts/joosy/core/joosy.js.coffee +65 -0
  13. data/app/assets/javascripts/joosy/core/layout.js.coffee +47 -0
  14. data/app/assets/javascripts/joosy/core/modules/container.js.coffee +59 -0
  15. data/app/assets/javascripts/joosy/core/modules/events.js.coffee +35 -0
  16. data/app/assets/javascripts/joosy/core/modules/filters.js.coffee +39 -0
  17. data/app/assets/javascripts/joosy/core/modules/log.js.coffee +15 -0
  18. data/app/assets/javascripts/joosy/core/modules/module.js.coffee +43 -0
  19. data/app/assets/javascripts/joosy/core/modules/renderer.js.coffee +116 -0
  20. data/app/assets/javascripts/joosy/core/modules/time_manager.js.coffee +25 -0
  21. data/app/assets/javascripts/joosy/core/modules/widgets_manager.js.coffee +58 -0
  22. data/app/assets/javascripts/joosy/core/page.js.coffee +156 -0
  23. data/app/assets/javascripts/joosy/core/preloader.js.coffee +5 -0
  24. data/app/assets/javascripts/joosy/core/resource/generic.js.coffee +61 -0
  25. data/app/assets/javascripts/joosy/core/resource/rest.js.coffee +76 -0
  26. data/app/assets/javascripts/joosy/core/resource/rest_collection.js.coffee +48 -0
  27. data/app/assets/javascripts/joosy/core/router.js.coffee +89 -0
  28. data/app/assets/javascripts/joosy/core/templaters/rails_jst.js.coffee +20 -0
  29. data/app/assets/javascripts/joosy/core/widget.js.coffee +41 -0
  30. data/app/assets/javascripts/joosy/preloaders/caching.js.coffee +94 -0
  31. data/app/assets/javascripts/joosy/preloaders/inline.js.coffee +55 -0
  32. data/app/helpers/joosy/sprockets_helper.rb +23 -0
  33. data/app/views/layouts/json_wrapper.json.erb +1 -0
  34. data/joosy.gemspec +25 -0
  35. data/lib/joosy.rb +9 -0
  36. data/lib/joosy/forms.rb +47 -0
  37. data/lib/joosy/rails/engine.rb +17 -0
  38. data/lib/joosy/rails/version.rb +5 -0
  39. data/lib/rails/generators/joosy/application_generator.rb +41 -0
  40. data/lib/rails/generators/joosy/joosy_base.rb +30 -0
  41. data/lib/rails/generators/joosy/layout_generator.rb +32 -0
  42. data/lib/rails/generators/joosy/page_generator.rb +44 -0
  43. data/lib/rails/generators/joosy/preloader_generator.rb +32 -0
  44. data/lib/rails/generators/joosy/resource_generator.rb +29 -0
  45. data/lib/rails/generators/joosy/templates/app.js.coffee +10 -0
  46. data/lib/rails/generators/joosy/templates/app/helpers/application.js.coffee +4 -0
  47. data/lib/rails/generators/joosy/templates/app/layouts/application.js.coffee +2 -0
  48. data/lib/rails/generators/joosy/templates/app/layouts/template.js.coffee +2 -0
  49. data/lib/rails/generators/joosy/templates/app/pages/application.js.coffee +1 -0
  50. data/lib/rails/generators/joosy/templates/app/pages/template.js.coffee +5 -0
  51. data/lib/rails/generators/joosy/templates/app/pages/welcome/index.js.coffee +22 -0
  52. data/lib/rails/generators/joosy/templates/app/resources/template.js.coffee +2 -0
  53. data/lib/rails/generators/joosy/templates/app/routes.js.coffee +8 -0
  54. data/lib/rails/generators/joosy/templates/app/templates/layouts/application.jst.hamlc +2 -0
  55. data/lib/rails/generators/joosy/templates/app/templates/pages/welcome/index.jst.hamlc +7 -0
  56. data/lib/rails/generators/joosy/templates/app/widgets/template.js.coffee +2 -0
  57. data/lib/rails/generators/joosy/templates/app_controller.rb +9 -0
  58. data/lib/rails/generators/joosy/templates/app_preloader.js.coffee.erb +13 -0
  59. data/lib/rails/generators/joosy/templates/preload.html.erb +26 -0
  60. data/lib/rails/generators/joosy/templates/preload.html.haml +19 -0
  61. data/lib/rails/generators/joosy/widget_generator.rb +32 -0
  62. data/spec/javascripts/helpers/spec_helper.js.coffee +44 -0
  63. data/spec/javascripts/joosy/core/application_spec.js.coffee +40 -0
  64. data/spec/javascripts/joosy/core/form_spec.js.coffee +141 -0
  65. data/spec/javascripts/joosy/core/joosy_spec.js.coffee +72 -0
  66. data/spec/javascripts/joosy/core/layout_spec.js.coffee +50 -0
  67. data/spec/javascripts/joosy/core/modules/container_spec.js.coffee +92 -0
  68. data/spec/javascripts/joosy/core/modules/events_spec.js.coffee +53 -0
  69. data/spec/javascripts/joosy/core/modules/filters_spec.js.coffee +71 -0
  70. data/spec/javascripts/joosy/core/modules/log_spec.js.coffee +15 -0
  71. data/spec/javascripts/joosy/core/modules/module_spec.js.coffee +47 -0
  72. data/spec/javascripts/joosy/core/modules/renderer_spec.js.coffee +149 -0
  73. data/spec/javascripts/joosy/core/modules/time_manager_spec.js.coffee +25 -0
  74. data/spec/javascripts/joosy/core/modules/widget_manager_spec.js.coffee +75 -0
  75. data/spec/javascripts/joosy/core/page_spec.js.coffee +175 -0
  76. data/spec/javascripts/joosy/core/resource/generic_spec.js.coffee +35 -0
  77. data/spec/javascripts/joosy/core/resource/rest_collection_spec.js.coffee +65 -0
  78. data/spec/javascripts/joosy/core/resource/rest_spec.js.coffee +108 -0
  79. data/spec/javascripts/joosy/core/router_spec.js.coffee +123 -0
  80. data/spec/javascripts/joosy/core/templaters/rails_jst_spec.js.coffee +25 -0
  81. data/spec/javascripts/joosy/core/widget_spec.js.coffee +51 -0
  82. data/spec/javascripts/joosy/preloaders/caching_spec.js.coffee +36 -0
  83. data/spec/javascripts/joosy/preloaders/inline_spec.js.coffee +16 -0
  84. data/spec/javascripts/support/assets/coolface.jpg +0 -0
  85. data/spec/javascripts/support/assets/okay.jpg +0 -0
  86. data/spec/javascripts/support/assets/test.js +1 -0
  87. data/spec/javascripts/support/jasmine.yml +74 -0
  88. data/spec/javascripts/support/jasmine_config.rb +23 -0
  89. data/spec/javascripts/support/jasmine_runner.rb +32 -0
  90. data/spec/javascripts/support/sinon-1.3.1.js +3469 -0
  91. data/spec/javascripts/support/sinon-ie-1.3.1.js +82 -0
  92. data/vendor/assets/javascripts/base64.js +135 -0
  93. data/vendor/assets/javascripts/inflection.js +656 -0
  94. data/vendor/assets/javascripts/jquery.form.js +980 -0
  95. data/vendor/assets/javascripts/jquery.hashchange.js +390 -0
  96. data/vendor/assets/javascripts/metamorph.js +409 -0
  97. data/vendor/assets/javascripts/sugar.js +6040 -0
  98. metadata +232 -0
@@ -0,0 +1,41 @@
1
+ #= require joosy/core/joosy
2
+ #= require joosy/core/modules/module
3
+ #= require joosy/core/modules/log
4
+ #= require joosy/core/modules/events
5
+ #= require joosy/core/modules/container
6
+ #= require joosy/core/modules/renderer
7
+ #= require joosy/core/modules/filters
8
+
9
+ class Joosy.Widget extends Joosy.Module
10
+ @include Joosy.Modules.Log
11
+ @include Joosy.Modules.Events
12
+ @include Joosy.Modules.Container
13
+ @include Joosy.Modules.Renderer
14
+ @include Joosy.Modules.Filters
15
+
16
+ __renderer: false
17
+
18
+ setInterval: (args...) ->
19
+ @parent.setInterval(args...)
20
+
21
+ setTimeout: (args...) ->
22
+ @parent.setTimeout(args...)
23
+
24
+ navigate: (args...) ->
25
+ Joosy.Router.navigate(args...)
26
+
27
+ __renderSection: ->
28
+ 'widgets'
29
+
30
+ __load: (@parent, @container) ->
31
+ if @__renderer
32
+ @container.html @__renderer()
33
+ @refreshElements()
34
+ @__delegateEvents()
35
+ @__runAfterLoads()
36
+
37
+ this
38
+
39
+ __unload: ->
40
+ @__removeMetamorphs()
41
+ @__runAfterUnloads()
@@ -0,0 +1,94 @@
1
+ #= require base64
2
+
3
+ window.globalEval = (src) ->
4
+ if window.execScript
5
+ window.execScript src
6
+ else
7
+ fn = ->
8
+ window.eval.call window,src
9
+ fn()
10
+
11
+ @Preloader = @CachingPreloader =
12
+ force: false
13
+ prefix: "cache:"
14
+
15
+ libraries: []
16
+ counter: 0
17
+
18
+ complete: false
19
+
20
+ ajax: (url, size, callback) ->
21
+ if window.XMLHttpRequest
22
+ x = new XMLHttpRequest
23
+ else
24
+ x = new ActiveXObject 'Microsoft.XMLHTTP'
25
+
26
+ x.open 'GET', url, 1
27
+
28
+ x.onreadystatechange = () =>
29
+ if x.readyState > 3
30
+ clearInterval(@interval)
31
+ callback?(x)
32
+
33
+ if @progress
34
+ poller = =>
35
+ try
36
+ @progress.call window, Math.round((x.responseText.length / size) * (@counter / @libraries.length) * 100)
37
+ catch e
38
+ # ... IE?
39
+
40
+ @interval = setInterval poller, 100
41
+
42
+ x.send()
43
+
44
+ restore: ->
45
+ for name, i in @libraries
46
+ code = window.localStorage.getItem(name)
47
+ window.globalEval if window.navigator.appName == "Microsoft Internet Explorer" then Base64.decode(code) else code
48
+ @complete?.call window, true
49
+
50
+ download: (libraries) ->
51
+ if libraries.length > 0
52
+ @counter += 1
53
+
54
+ lib = libraries.shift()
55
+ url = lib[0]
56
+ size = lib[1]
57
+
58
+ @ajax url, size, (xhr) =>
59
+ code = xhr.responseText
60
+ window.localStorage.setItem @prefix+url, (if window.navigator.appName == "Microsoft Internet Explorer" then Base64.encode(code) else code)
61
+ window.globalEval xhr.responseText
62
+ @download libraries
63
+ else
64
+ @clean()
65
+ @complete?.call window
66
+
67
+ load: (libraries, options={}) ->
68
+ @[key] = val for key, val of options
69
+ @libraries = libraries.slice()
70
+
71
+ for lib, i in @libraries
72
+ @libraries[i] = @prefix+lib[0]
73
+
74
+ if !@force && @check()
75
+ @restore()
76
+ else
77
+ @start?.call window
78
+ @download libraries
79
+
80
+ check: ->
81
+ flag = true
82
+ for name, i in @libraries
83
+ flag &&= window.localStorage.getItem(name)?
84
+ flag
85
+
86
+ clean: ->
87
+ removed = 0
88
+
89
+ for element, i in window.localStorage
90
+ key = window.localStorage.key(i-removed)
91
+
92
+ if key.indexOf(@prefix) == 0 && @libraries.indexOf(key) < 0
93
+ window.localStorage.removeItem key
94
+ removed += 1
@@ -0,0 +1,55 @@
1
+ #
2
+ # Preloader for libraries using <script src> without any caching magic
3
+ #
4
+ # Example:
5
+ # libraries = [['/test1.js'], ['/test2.js']]
6
+ # InlinePreloader.load libraries,
7
+ # start: -> console.log 'preloading started'
8
+ # complete: -> console.log 'preloading finished'
9
+ #
10
+ # @class InlinePreloader
11
+ #
12
+ @Preloader = @InlinePreloader =
13
+
14
+ #
15
+ # Loads one script by adding <script src> to DOM head
16
+ #
17
+ # @param [String] url to load script from
18
+ # @param [Function] `() -> null` to call after script was loaded and executed
19
+ #
20
+ receive: (url, callback) ->
21
+ head = document.getElementsByTagName("head")[0]
22
+ script = document.createElement("script")
23
+ script.src = url
24
+
25
+ done = false
26
+
27
+ proceed = ->
28
+ if ( !done && (!this.readyState ||
29
+ this.readyState == "loaded" || this.readyState == "complete") )
30
+
31
+ done = true and callback() if callback
32
+ script.onload = script.onreadystatechange = null
33
+
34
+ script.onload = script.onreadystatechange = proceed
35
+
36
+ head.appendChild(script)
37
+ return undefined
38
+
39
+ #
40
+ # Loads set of libraries by adding <script src> to DOM head
41
+ # See class description for example of usage
42
+ #
43
+ # @param [Array] 2-levels array of libraries URLs i.e. [['/test1.js'],['/test2.js']]
44
+ # @param [Hash] Available options:
45
+ # * start: `() -> null` to call before load starts:
46
+ # * complete: `() -> null` to call after load completes
47
+ #
48
+ load: (libraries, options) ->
49
+ @[key] = val for key, val of options
50
+ @start?.call window
51
+
52
+ if libraries.length > 0
53
+ @receive libraries.shift()[0], => @load(libraries)
54
+ else
55
+ @complete?.call window
@@ -0,0 +1,23 @@
1
+ module Joosy::SprocketsHelper
2
+ def extract_sources_and_sizes_from_include_tag(name)
3
+ code = javascript_include_tag name
4
+ resources = code.scan(/(?:href|src)=['"]([^'"]+)['"]/).flatten
5
+
6
+ resources.map do |resource|
7
+ path = ::Rails.root.to_s + "/public" + resource.split('?')[0]
8
+ size = File.size(path) rescue false
9
+ [resource, size]
10
+ end.to_json.html_safe
11
+ end
12
+
13
+ def require_joosy_preloader_for(app_asset, options={})
14
+ preloader_asset = "joosy/preloaders/#{options[:preloader] || 'caching'}"
15
+ force_preloader = options[:force] || false
16
+
17
+ if force_preloader
18
+ require_asset preloader_asset
19
+ else
20
+ require_asset Rails.env == 'production' ? preloader_asset : app_asset
21
+ end
22
+ end
23
+ end
@@ -0,0 +1 @@
1
+ <textarea><%= yield %></textarea>
data/joosy.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ require File.expand_path("../lib/joosy/rails/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "joosy"
5
+ s.version = Joosy::Rails::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.summary = "Joosy Framework support for Ruby on Rails"
8
+ s.email = "boris@roundlake.ru"
9
+ s.homepage = "http://github.com/roundlake/joosy"
10
+ s.description = "A gem wrapper to include Joosy via the asset pipeline."
11
+ s.authors = ['Boris Staal', 'Andrew Shaydurov', 'Peter Zotov', 'Alexander Pavlenko']
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.require_paths = ["lib"]
15
+
16
+ s.add_dependency 'rails', ">= 3.1.0"
17
+ s.add_dependency 'coffee-rails'
18
+ s.add_dependency 'jquery-rails'
19
+ s.add_dependency 'haml_coffee_assets'
20
+
21
+ s.add_development_dependency 'guard'
22
+ s.add_development_dependency 'guard-coffeescript'
23
+ s.add_development_dependency 'guard-sprockets'
24
+ s.add_development_dependency 'jasmine'
25
+ end
data/lib/joosy.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'haml_coffee_assets'
2
+ require 'jquery-rails'
3
+ require 'coffee-rails'
4
+
5
+ require 'joosy/rails/engine'
6
+ require 'joosy/rails/version'
7
+ require 'joosy/forms'
8
+
9
+ ActionController::Base.send :include, Joosy::Forms
@@ -0,0 +1,47 @@
1
+ module Joosy
2
+ module Forms
3
+ def self.included base
4
+ base.send(:include, InstanceMethods)
5
+ end
6
+
7
+ module InstanceMethods
8
+ def joosy_store(entity, data=nil, &block)
9
+ if entity.save
10
+ joosy_succeed(data, entity, &block)
11
+ else
12
+ joosy_fail(entity.errors.messages, entity.class.name)
13
+ end
14
+ end
15
+
16
+ def joosy_fail(errors, entity=false)
17
+ errors = Hash[*errors.map {|x| [x, nil]}.flatten] if errors.is_a?(Array)
18
+
19
+ if !entity
20
+ notifications = errors
21
+ else
22
+ notifications = {}
23
+ errors.each do |k, v|
24
+ notifications["#{entity.underscore}[#{k}]"] = v
25
+ end
26
+ end
27
+
28
+ joosy_respond notifications, :unprocessable_entity
29
+ end
30
+
31
+ def joosy_succeed(data, entity=nil, &block)
32
+ block.call(entity) if block_given?
33
+ joosy_respond (data.is_a?(Proc) ? data.call(entity) : (data || entity))
34
+ end
35
+
36
+ def joosy_respond(data, status=200)
37
+ unless request.xhr?
38
+ @data = { :status => status, :json => data }
39
+ self.class.layout 'json_wrapper'
40
+ render :text => result.to_json, :status => status
41
+ else
42
+ render :json => data, :status => status
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ require 'rails/engine'
2
+
3
+ module Joosy
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+
7
+ initializer 'joosy.extend.sprockets' do |app|
8
+ ActiveSupport.on_load(:action_view) do
9
+ app.assets.context_class.instance_eval do
10
+ include ::Joosy::SprocketsHelper
11
+ end
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ module Joosy
2
+ module Rails
3
+ VERSION = "0.1.0.RC1"
4
+ end
5
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails/generators/joosy/joosy_base'
2
+
3
+ module Joosy
4
+ module Generators
5
+ class ApplicationGenerator < ::Rails::Generators::JoosyBase
6
+ source_root File.join(File.dirname(__FILE__), 'templates')
7
+
8
+ def create_files
9
+ super
10
+
11
+ template "app.js.coffee", "#{file_path}.js.coffee"
12
+
13
+ empty_directory file_path
14
+
15
+ template "app/routes.js.coffee", "#{file_path}/routes.js.coffee"
16
+
17
+ empty_directory "#{file_path}/helpers"
18
+ template "app/helpers/application.js.coffee", "#{file_path}/helpers/application.js.coffee"
19
+
20
+ empty_directory "#{file_path}/layouts"
21
+ template "app/layouts/application.js.coffee", "#{file_path}/layouts/application.js.coffee"
22
+
23
+ empty_directory "#{file_path}/pages/welcome"
24
+ template "app/pages/application.js.coffee", "#{file_path}/pages/application.js.coffee"
25
+ template "app/pages/welcome/index.js.coffee", "#{file_path}/pages/welcome/index.js.coffee"
26
+
27
+ empty_directory "#{file_path}/templates/layouts"
28
+ template "app/templates/layouts/application.jst.hamlc", "#{file_path}/templates/layouts/application.jst.hamlc"
29
+
30
+ empty_directory "#{file_path}/templates/pages/welcome"
31
+ template "app/templates/pages/welcome/index.jst.hamlc", "#{file_path}/templates/pages/welcome/index.jst.hamlc"
32
+
33
+ empty_directory_with_gitkeep "#{file_path}/widgets"
34
+ empty_directory_with_gitkeep "#{file_path}/resources"
35
+
36
+ empty_directory_with_gitkeep "#{file_path}/templates/layouts"
37
+ empty_directory_with_gitkeep "#{file_path}/templates/widgets"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ require 'rails/generators/named_base'
2
+
3
+ module Rails
4
+ module Generators
5
+ class JoosyBase < ::Rails::Generators::NamedBase
6
+ class_option :template_kind, :type => :string, :aliases => "-T", :default => 'hamlc',
7
+ :desc => "Generate templates with specified extension (default: .hamlc)"
8
+
9
+ class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false,
10
+ :desc => "Skip Git keeps"
11
+
12
+
13
+ def create_files
14
+ self.destination_root = "app/assets/javascripts"
15
+ end
16
+
17
+ protected
18
+
19
+ # From https://github.com/rails/rails/blob/master/railties/lib/rails/generators/app_base.rb
20
+ def empty_directory_with_gitkeep(destination, config = {})
21
+ empty_directory(destination, config)
22
+ git_keep(destination)
23
+ end
24
+
25
+ def git_keep(destination)
26
+ create_file("#{destination}/.gitkeep") unless options[:skip_git]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/generators/joosy/joosy_base'
2
+
3
+ module Joosy
4
+ module Generators
5
+ class LayoutGenerator < ::Rails::Generators::JoosyBase
6
+ source_root File.join(File.dirname(__FILE__), 'templates')
7
+
8
+ def create_files
9
+ super
10
+
11
+ empty_directory "#{app_path}/layouts"
12
+ template "app/layouts/template.js.coffee", "#{app_path}/layouts/#{file_name}.js.coffee"
13
+
14
+ empty_directory "#{app_path}/templates/layouts"
15
+ create_file("#{app_path}/templates/layouts/#{file_name}.jst.#{options[:template_kind]}")
16
+ end
17
+
18
+ protected
19
+
20
+ def app_path
21
+ unless class_path.size == 1
22
+ puts <<HELP
23
+ Usage: rails generate joosy:layout joosy_app_name/layout_name
24
+ Tip: do not add Layout suffix to layout_name
25
+ HELP
26
+ exit 1
27
+ end
28
+ class_path[0]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ require 'rails/generators/joosy/joosy_base'
2
+
3
+ module Joosy
4
+ module Generators
5
+ class PageGenerator < ::Rails::Generators::JoosyBase
6
+ source_root File.join(File.dirname(__FILE__), 'templates')
7
+
8
+ def create_files
9
+ super
10
+
11
+ empty_directory "#{app_path}/pages/#{namespace_path}"
12
+ template "app/pages/template.js.coffee", "#{app_path}/pages/#{namespace_path}/#{file_name}.js.coffee"
13
+
14
+ empty_directory "#{app_path}/templates/pages/#{namespace_path}"
15
+ create_file "#{app_path}/templates/pages/#{namespace_path}/#{file_name}.jst.#{options[:template_kind]}"
16
+ end
17
+
18
+ protected
19
+
20
+ def app_path
21
+ if class_path.size < 2
22
+ puts <<HELP
23
+ Usage: rails generate joosy:page joosy_app_name/page_namespace/page_name
24
+ Tip: do not add Page suffix to page_name
25
+ HELP
26
+ exit 1
27
+ end
28
+ class_path[0]
29
+ end
30
+
31
+ def namespace_path
32
+ File.join class_path[1..-1]
33
+ end
34
+
35
+ def namespace_name
36
+ class_path[1..-1].map(&:camelize).join '.'
37
+ end
38
+
39
+ def layout_name
40
+ class_path[1]
41
+ end
42
+ end
43
+ end
44
+ end