hyperstack-config 0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +29 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +303 -0
- data/README.md +73 -0
- data/Rakefile +11 -0
- data/bin/hyperstack-hotloader +22 -0
- data/hyperstack-config.gemspec +45 -0
- data/lib/hyperstack-config.rb +45 -0
- data/lib/hyperstack-hotloader-config.js.erb +7 -0
- data/lib/hyperstack-loader-application.rb.erb +1 -0
- data/lib/hyperstack-loader-system-code.rb.erb +1 -0
- data/lib/hyperstack-loader.js +6 -0
- data/lib/hyperstack-prerender-loader-application.rb.erb +1 -0
- data/lib/hyperstack-prerender-loader-system-code.rb.erb +1 -0
- data/lib/hyperstack-prerender-loader.js +4 -0
- data/lib/hyperstack/active_support_string_inquirer.rb +32 -0
- data/lib/hyperstack/autoloader.rb +140 -0
- data/lib/hyperstack/autoloader_starter.rb +15 -0
- data/lib/hyperstack/boot.rb +41 -0
- data/lib/hyperstack/client_readers.rb +19 -0
- data/lib/hyperstack/client_stubs.rb +12 -0
- data/lib/hyperstack/config/version.rb +5 -0
- data/lib/hyperstack/config_settings.rb +49 -0
- data/lib/hyperstack/context.rb +36 -0
- data/lib/hyperstack/deprecation_warning.rb +10 -0
- data/lib/hyperstack/env.rb +10 -0
- data/lib/hyperstack/environment/development/hyperstack_env.rb +5 -0
- data/lib/hyperstack/environment/production/hyperstack_env.rb +5 -0
- data/lib/hyperstack/environment/staging/hyperstack_env.rb +5 -0
- data/lib/hyperstack/environment/test/hyperstack_env.rb +5 -0
- data/lib/hyperstack/hotloader.rb +129 -0
- data/lib/hyperstack/hotloader/add_error_boundry.rb +31 -0
- data/lib/hyperstack/hotloader/css_reloader.rb +41 -0
- data/lib/hyperstack/hotloader/server.rb +296 -0
- data/lib/hyperstack/hotloader/short_cut.js +9 -0
- data/lib/hyperstack/hotloader/socket.rb +136 -0
- data/lib/hyperstack/hotloader/stack-trace.js +2977 -0
- data/lib/hyperstack/hotloader/stub.rb +10 -0
- data/lib/hyperstack/imports.rb +104 -0
- data/lib/hyperstack/js_imports.rb +18 -0
- data/lib/hyperstack/on_client.rb +5 -0
- data/lib/hyperstack/on_error.rb +5 -0
- data/lib/hyperstack/rail_tie.rb +87 -0
- data/lib/hyperstack/string.rb +6 -0
- metadata +350 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require "hyperstack/hotloader/server"
|
5
|
+
|
6
|
+
options = {:port => 25222, :directories => []}
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: opal-hot-reloader [options]"
|
9
|
+
|
10
|
+
opts.on("-p", '--port [INTEGER]', Integer, 'port to run on, defaults to 25222') do |v|
|
11
|
+
options[:port] = v
|
12
|
+
end
|
13
|
+
|
14
|
+
opts.on("-d", '--directories x,y,z', Array, "comma separated directories to watch. Ex. to add 2 directories '-d app/assets/js,app/client/components'. Directoriess automatically included if they exist are:\n\t\t* app/assets/javascripts\n\t\t* app/views/components") do |v|
|
15
|
+
options[:directories] = v
|
16
|
+
end
|
17
|
+
|
18
|
+
end.parse!
|
19
|
+
|
20
|
+
server = Hyperstack::Hotloader::Server.new(options)
|
21
|
+
puts "Listening on port #{options[:port]}, watching for changes in #{options[:directories].join(', ')}"
|
22
|
+
server.loop
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hyperstack/config/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'hyperstack-config'
|
8
|
+
spec.version = Hyperstack::Config::VERSION
|
9
|
+
spec.authors = ['Mitch VanDuyn', 'Jan Biedermann']
|
10
|
+
spec.email = ['mitch@catprint.com', 'jan@kursator.com']
|
11
|
+
spec.summary = %q{Provides a single point configuration module for hyperstack gems}
|
12
|
+
spec.homepage = 'http://ruby-hyperstack.org'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
# spec.metadata = {
|
15
|
+
# "homepage_uri" => 'http://ruby-hyperstack.org',
|
16
|
+
# "source_code_uri" => 'https://github.com/ruby-hyperstack/hyper-component'
|
17
|
+
# }
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
#spec.bindir = 'exe'
|
21
|
+
#spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.executables << 'hyperstack-hotloader'
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'libv8', '~> 6.3.0' # see https://github.com/discourse/mini_racer/issues/92
|
26
|
+
spec.add_dependency 'listen', '~> 3.0' # for hot loader
|
27
|
+
spec.add_dependency 'mini_racer', '~> 0.1.15'
|
28
|
+
spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
|
29
|
+
spec.add_dependency 'opal-browser', '~> 0.2.0'
|
30
|
+
spec.add_dependency 'uglifier'
|
31
|
+
spec.add_dependency 'websocket' # for hot loader
|
32
|
+
|
33
|
+
|
34
|
+
spec.add_development_dependency 'bundler', '~> 1.16.0'
|
35
|
+
spec.add_development_dependency 'chromedriver-helper'
|
36
|
+
spec.add_development_dependency 'opal-rails', '~> 0.9.4'
|
37
|
+
spec.add_development_dependency 'pry'
|
38
|
+
spec.add_development_dependency 'puma'
|
39
|
+
spec.add_development_dependency 'rails', '>= 4.0.0'
|
40
|
+
spec.add_development_dependency 'rake'
|
41
|
+
spec.add_development_dependency 'rspec', '~> 3.7.0'
|
42
|
+
spec.add_development_dependency 'rubocop', '~> 0.51.0'
|
43
|
+
spec.add_development_dependency 'sqlite3'
|
44
|
+
spec.add_development_dependency 'timecop', '~> 0.8.1'
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'hyperstack/boot'
|
2
|
+
if RUBY_ENGINE == 'opal'
|
3
|
+
require 'hyperstack/deprecation_warning'
|
4
|
+
require 'hyperstack/string'
|
5
|
+
require 'hyperstack/client_stubs'
|
6
|
+
require 'hyperstack/context'
|
7
|
+
require 'hyperstack/js_imports'
|
8
|
+
require 'hyperstack/on_client'
|
9
|
+
require 'hyperstack/active_support_string_inquirer.rb'
|
10
|
+
require 'hyperstack_env'
|
11
|
+
require 'hyperstack/hotloader/stub'
|
12
|
+
else
|
13
|
+
require 'opal'
|
14
|
+
require 'opal-browser'
|
15
|
+
# We need opal-rails to be loaded for Gem code to be properly included by sprockets.
|
16
|
+
begin
|
17
|
+
require 'opal-rails' if defined? Rails
|
18
|
+
rescue LoadError
|
19
|
+
puts "****** WARNING: To use Hyperstack with Rails you must include the 'opal-rails' gem in your gem file."
|
20
|
+
end
|
21
|
+
require 'hyperstack/config_settings'
|
22
|
+
require 'hyperstack/context'
|
23
|
+
require 'hyperstack/imports'
|
24
|
+
require 'hyperstack/js_imports'
|
25
|
+
require 'hyperstack/client_readers'
|
26
|
+
require 'hyperstack/on_client'
|
27
|
+
|
28
|
+
if defined? Rails
|
29
|
+
require 'hyperstack/rail_tie'
|
30
|
+
end
|
31
|
+
require 'hyperstack/active_support_string_inquirer.rb' unless defined? ActiveSupport
|
32
|
+
require 'hyperstack/env'
|
33
|
+
require 'hyperstack/on_error'
|
34
|
+
Hyperstack.define_setting :hotloader_port, 25222
|
35
|
+
Hyperstack.define_setting :hotloader_ping, nil
|
36
|
+
Hyperstack.import 'opal', gem: true
|
37
|
+
Hyperstack.import 'browser', client_only: true
|
38
|
+
Hyperstack.import 'hyperstack-config', gem: true
|
39
|
+
Hyperstack.import 'hyperstack/autoloader'
|
40
|
+
Hyperstack.import 'hyperstack/autoloader_starter'
|
41
|
+
# based on the environment pick the directory containing the file with the matching
|
42
|
+
# value for the client. This avoids use of ERB for builds outside of sprockets environment
|
43
|
+
Opal.append_path(File.expand_path("../hyperstack/environment/#{Hyperstack.env}/", __FILE__).untaint)
|
44
|
+
Opal.append_path(File.expand_path('../', __FILE__).untaint)
|
45
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
console.log('hotloader config doing its thing')
|
2
|
+
if ((typeof(window) !== 'undefined') && (window.Hyperstack==undefined || window.Hyperstack.hotloader==undefined)) {
|
3
|
+
window.Hyperstack = { hotloader: function(port, ping) { }}
|
4
|
+
}
|
5
|
+
//if (typeof(Hyperstack) === 'undefined') { Hyperstack = {} }
|
6
|
+
Hyperstack.hotloader.port = <%= Hyperstack.hotloader_port %>
|
7
|
+
Hyperstack.hotloader.ping = <%= !!Hyperstack.hotloader_ping %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= Hyperstack.generate_requires(:client, false, __FILE__) %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= Hyperstack.generate_requires(:client, true, __FILE__) %>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
//= require hyperstack-loader-system-code
|
2
|
+
//= require hyperstack-loader-application
|
3
|
+
//= require hyperstack-hotloader-config
|
4
|
+
Opal.load('hyperstack-loader-system-code')
|
5
|
+
Opal.load('hyperstack-loader-application')
|
6
|
+
Hyperstack.hotloader(Hyperstack.hotloader.port, Hyperstack.hotloader.ping)
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= Hyperstack.generate_requires(:server, false, __FILE__) %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= Hyperstack.generate_requires(:server, true, __FILE__) %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
# Wrapping a string in this class gives you a prettier way to test
|
3
|
+
# for equality. The value returned by <tt>Rails.env</tt> is wrapped
|
4
|
+
# in a StringInquirer object, so instead of calling this:
|
5
|
+
#
|
6
|
+
# Rails.env == 'production'
|
7
|
+
#
|
8
|
+
# you can call this:
|
9
|
+
#
|
10
|
+
# Rails.env.production?
|
11
|
+
#
|
12
|
+
# == Instantiating a new StringInquirer
|
13
|
+
#
|
14
|
+
# vehicle = ActiveSupport::StringInquirer.new('car')
|
15
|
+
# vehicle.car? # => true
|
16
|
+
# vehicle.bike? # => false
|
17
|
+
class StringInquirer < String
|
18
|
+
private
|
19
|
+
|
20
|
+
def respond_to_missing?(method_name, include_private = false)
|
21
|
+
(method_name[-1] == "?") || super
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(method_name, *arguments)
|
25
|
+
if method_name[-1] == "?"
|
26
|
+
self == method_name[0..-2]
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Hyperstack
|
4
|
+
class Autoloader
|
5
|
+
# All files ever loaded.
|
6
|
+
def self.history=(a)
|
7
|
+
@@history = a
|
8
|
+
end
|
9
|
+
def self.history
|
10
|
+
@@history
|
11
|
+
end
|
12
|
+
self.history = Set.new
|
13
|
+
|
14
|
+
def self.load_paths=(a)
|
15
|
+
@@load_paths = a
|
16
|
+
end
|
17
|
+
def self.load_paths
|
18
|
+
@@load_paths
|
19
|
+
end
|
20
|
+
self.load_paths = []
|
21
|
+
|
22
|
+
def self.loaded=(a)
|
23
|
+
@@loaded = a
|
24
|
+
end
|
25
|
+
def self.loaded
|
26
|
+
@@loaded
|
27
|
+
end
|
28
|
+
self.loaded = Set.new
|
29
|
+
|
30
|
+
def self.loading=(a)
|
31
|
+
@@loading = a
|
32
|
+
end
|
33
|
+
def self.loading
|
34
|
+
@@loading
|
35
|
+
end
|
36
|
+
self.loading = []
|
37
|
+
|
38
|
+
def self.const_missing(const_name, mod)
|
39
|
+
# name.nil? is testing for anonymous
|
40
|
+
from_mod = mod.name.nil? ? guess_for_anonymous(const_name) : mod
|
41
|
+
load_missing_constant(from_mod, const_name)
|
42
|
+
rescue Exception => e
|
43
|
+
puts "HyperStack autoloader failed attempting to load #{mod}::#{const_name}. Could be a bug in autoloader"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.guess_for_anonymous(const_name)
|
47
|
+
if Object.const_defined?(const_name)
|
48
|
+
raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name
|
49
|
+
else
|
50
|
+
Object
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.load_missing_constant(from_mod, const_name)
|
55
|
+
# see active_support/dependencies.rb in case of reloading on how to handle
|
56
|
+
qualified_name = qualified_name_for(from_mod, const_name)
|
57
|
+
qualified_path = underscore(qualified_name)
|
58
|
+
|
59
|
+
module_path = search_for_module(qualified_path)
|
60
|
+
if module_path
|
61
|
+
if loading.include?(module_path)
|
62
|
+
raise "Circular dependency detected while autoloading constant #{qualified_name}"
|
63
|
+
else
|
64
|
+
require_or_load(from_mod, module_path)
|
65
|
+
raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{module_path} to define it" unless from_mod.const_defined?(const_name, false)
|
66
|
+
return from_mod.const_get(const_name)
|
67
|
+
end
|
68
|
+
elsif from_mod.respond_to?(:parent) && (parent = from_mod.parent) && parent != from_mod &&
|
69
|
+
! from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
|
70
|
+
begin
|
71
|
+
return parent.const_missing(const_name)
|
72
|
+
rescue NameError => e
|
73
|
+
raise unless missing_name?(e, qualified_name_for(parent, const_name))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.missing_name?(e, name)
|
79
|
+
mn = if /undefined/ !~ e.message
|
80
|
+
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ e.message
|
81
|
+
end
|
82
|
+
mn == name
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the constant path for the provided parent and constant name.
|
86
|
+
def self.qualified_name_for(mod, name)
|
87
|
+
mod_name = to_constant_name(mod)
|
88
|
+
mod_name == 'Object' ? name.to_s : "#{mod_name}::#{name}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.require_or_load(from_mod, module_path)
|
92
|
+
return if loaded.include?(module_path)
|
93
|
+
loaded << module_path
|
94
|
+
loading << module_path
|
95
|
+
|
96
|
+
begin
|
97
|
+
result = require module_path
|
98
|
+
rescue Exception
|
99
|
+
loaded.delete module_path
|
100
|
+
raise LoadError, "Unable to autoload: require_or_load #{module_path} failed"
|
101
|
+
ensure
|
102
|
+
loading.pop
|
103
|
+
end
|
104
|
+
|
105
|
+
# Record history *after* loading so first load gets warnings.
|
106
|
+
history << module_path
|
107
|
+
result
|
108
|
+
# end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.search_for_module(path)
|
112
|
+
# oh my! imagine Bart Simpson, writing on the board:
|
113
|
+
# "javascript is not ruby, javascript is not ruby, javascript is not ruby, ..."
|
114
|
+
# then running home, starting irb, on the fly developing a chat client and opening a session with Homer at his workplace: "Hi Dad ..."
|
115
|
+
load_paths.each do |load_path|
|
116
|
+
mod_path = load_path + '/' + path
|
117
|
+
return mod_path if `Opal.modules.hasOwnProperty(#{mod_path})`
|
118
|
+
end
|
119
|
+
return path if `Opal.modules.hasOwnProperty(#{path})`
|
120
|
+
nil # Gee, I sure wish we had first_match ;-)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Convert the provided const desc to a qualified constant name (as a string).
|
124
|
+
# A module, class, symbol, or string may be provided.
|
125
|
+
def self.to_constant_name(desc) #:nodoc:
|
126
|
+
case desc
|
127
|
+
when String then desc.sub(/^::/, '')
|
128
|
+
when Symbol then desc.to_s
|
129
|
+
when Module
|
130
|
+
desc.name ||
|
131
|
+
raise(ArgumentError, 'Anonymous modules have no name to be referenced by')
|
132
|
+
else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.underscore(string)
|
137
|
+
string.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Hyperstack::Autoloader.load_paths = %w[components models operations stores]
|
2
|
+
|
3
|
+
class Object
|
4
|
+
class << self
|
5
|
+
alias _autoloader_original_const_missing const_missing
|
6
|
+
|
7
|
+
def const_missing(const_name)
|
8
|
+
# need to call original code because some things are set up there
|
9
|
+
# original code may also be overloaded by reactrb, for example
|
10
|
+
_autoloader_original_const_missing(const_name)
|
11
|
+
rescue StandardError => e
|
12
|
+
Hyperstack::Autoloader.const_missing(const_name, self) || raise(e)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Define a primitive Boot Operation that will act like a full blown operation.
|
2
|
+
# If Operation is defined before this then we skip the whole exercise. Likewise
|
3
|
+
# when Operation defines the Boot class, it will check for a receivers method and
|
4
|
+
# copy any defined receivers into the updated Boot class.
|
5
|
+
|
6
|
+
module Hyperstack
|
7
|
+
unless defined? Operation
|
8
|
+
class Operation
|
9
|
+
end
|
10
|
+
class Application
|
11
|
+
class Boot < Operation
|
12
|
+
class ReactDummyParams
|
13
|
+
# behaves simplistically like a true Operation broadcast Params object with a
|
14
|
+
# single param named context.
|
15
|
+
attr_reader :context
|
16
|
+
def initialize(context)
|
17
|
+
@context = context
|
18
|
+
end
|
19
|
+
end
|
20
|
+
class << self
|
21
|
+
def on_dispatch(&block)
|
22
|
+
receivers << block
|
23
|
+
end
|
24
|
+
|
25
|
+
def receivers
|
26
|
+
# use the force: true option so that system code needing to receive
|
27
|
+
# boot will NOT be erased on the next Hyperloop::Context.reset!
|
28
|
+
Hyperstack::Context.set_var(self, :@receivers, force: true) { [] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(context: nil)
|
32
|
+
params = ReactDummyParams.new(context)
|
33
|
+
receivers.each do |receiver|
|
34
|
+
receiver.call params
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Hyperstack
|
2
|
+
# configuration utility
|
3
|
+
class << self
|
4
|
+
def client_readers
|
5
|
+
@client_readers ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def client_reader_hash
|
9
|
+
@client_readers_hash ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def client_reader(*args)
|
13
|
+
# configuration.client_reader[:foo] = 12 initialize your own client value
|
14
|
+
# configuration.client_reader :foo, :bar make previous setting readable on client
|
15
|
+
client_readers += [*args]
|
16
|
+
client_reader_hash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Hyperstack
|
2
|
+
# configuration utility
|
3
|
+
class << self
|
4
|
+
def initialized_blocks
|
5
|
+
@initialized_blocks ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def reset_blocks
|
9
|
+
@reset_blocks ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def configuration
|
13
|
+
reset_blocks.each(&:call)
|
14
|
+
yield self
|
15
|
+
initialized_blocks.each(&:call)
|
16
|
+
end
|
17
|
+
|
18
|
+
def define_setting(name, default = nil, &block)
|
19
|
+
class_variable_set("@@#{name}", default)
|
20
|
+
|
21
|
+
define_class_method "#{name}=" do |value|
|
22
|
+
class_variable_set("@@#{name}", value)
|
23
|
+
block.call value if block
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
define_class_method name do
|
28
|
+
class_variable_get("@@#{name}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def on_config_reset &block
|
34
|
+
reset_blocks << block
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_config_initialized &block
|
38
|
+
initialized_blocks << block
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def define_class_method(name, &block)
|
44
|
+
(class << self; self; end).instance_eval do
|
45
|
+
define_method name, &block
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|