jets 0.8.18 → 0.9.0

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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/CHANGELOG.md +20 -1
  4. data/Gemfile.lock +6 -2
  5. data/README/prerelease.md +6 -0
  6. data/README/testing.md +41 -0
  7. data/Rakefile +9 -1
  8. data/jets.gemspec +1 -0
  9. data/lib/jets.rb +17 -18
  10. data/lib/jets/application.rb +26 -3
  11. data/lib/jets/aws_services.rb +26 -59
  12. data/lib/jets/aws_services/stack_status.rb +52 -0
  13. data/lib/jets/builders.rb +3 -2
  14. data/lib/jets/builders/handler_generator.rb +38 -2
  15. data/lib/jets/builders/shared_deducer.rb +32 -0
  16. data/lib/jets/cfn/builders.rb +3 -1
  17. data/lib/jets/cfn/builders/api_deployment_builder.rb +1 -1
  18. data/lib/jets/cfn/builders/api_gateway_builder.rb +1 -1
  19. data/lib/jets/cfn/builders/base_child_builder.rb +37 -7
  20. data/lib/jets/cfn/builders/controller_builder.rb +6 -1
  21. data/lib/jets/cfn/builders/function_builder.rb +5 -0
  22. data/lib/jets/cfn/builders/interface.rb +5 -6
  23. data/lib/jets/cfn/builders/job_builder.rb +5 -0
  24. data/lib/jets/cfn/builders/parent_builder.rb +17 -16
  25. data/lib/jets/cfn/builders/rule_builder.rb +6 -1
  26. data/lib/jets/cfn/builders/shared_builder.rb +14 -0
  27. data/lib/jets/commands.rb +9 -8
  28. data/lib/jets/commands/build.rb +36 -14
  29. data/lib/jets/commands/console.rb +1 -0
  30. data/lib/jets/commands/help/runner.md +17 -0
  31. data/lib/jets/commands/main.rb +7 -0
  32. data/lib/jets/commands/new.rb +39 -19
  33. data/lib/jets/commands/runner.rb +18 -0
  34. data/lib/jets/commands/sequence.rb +27 -1
  35. data/lib/jets/commands/templates/skeleton/.rspec +3 -0
  36. data/lib/jets/commands/templates/skeleton/Gemfile.tt +0 -1
  37. data/lib/jets/commands/templates/skeleton/app/jobs/application_job.rb +2 -0
  38. data/lib/jets/commands/templates/skeleton/config/application.rb.tt +2 -1
  39. data/lib/jets/commands/templates/skeleton/config/routes.rb +5 -1
  40. data/lib/jets/commands/templates/skeleton/spec/spec_helper.rb.tt +5 -4
  41. data/lib/jets/core.rb +8 -6
  42. data/lib/jets/default/application.rb +1 -0
  43. data/lib/jets/generator.rb +1 -1
  44. data/lib/jets/inflections.rb +23 -0
  45. data/lib/jets/job/dsl.rb +69 -47
  46. data/lib/jets/klass.rb +6 -1
  47. data/lib/jets/lambda/dsl.rb +102 -34
  48. data/lib/jets/lambda/function_constructor.rb +2 -2
  49. data/lib/jets/lambda/task.rb +10 -5
  50. data/lib/jets/naming.rb +13 -2
  51. data/lib/jets/processors/deducer.rb +13 -2
  52. data/lib/jets/processors/main_processor.rb +1 -1
  53. data/lib/jets/rails_overrides.rb +1 -1
  54. data/lib/jets/resource.rb +20 -5
  55. data/lib/jets/resource/api_gateway/deployment.rb +0 -1
  56. data/lib/jets/resource/associated.rb +26 -0
  57. data/lib/jets/resource/base.rb +12 -0
  58. data/lib/jets/resource/child_stack.rb +2 -0
  59. data/lib/jets/resource/child_stack/api_deployment.rb +9 -15
  60. data/lib/jets/resource/child_stack/api_gateway.rb +8 -8
  61. data/lib/jets/resource/child_stack/app_class.rb +41 -16
  62. data/lib/jets/resource/child_stack/base.rb +24 -0
  63. data/lib/jets/resource/child_stack/shared.rb +90 -0
  64. data/lib/jets/resource/config.rb +4 -0
  65. data/lib/jets/resource/config/config_rule.rb +66 -0
  66. data/lib/jets/resource/config/managed_rule.rb +15 -0
  67. data/lib/jets/resource/events.rb +3 -0
  68. data/lib/jets/resource/events/rule.rb +31 -0
  69. data/lib/jets/resource/iam/application_role.rb +2 -2
  70. data/lib/jets/resource/iam/base_role_definition.rb +4 -2
  71. data/lib/jets/resource/iam/class_role.rb +50 -2
  72. data/lib/jets/resource/iam/function_role.rb +28 -0
  73. data/lib/jets/resource/iam/policy_document.rb +0 -4
  74. data/lib/jets/resource/permission.rb +12 -6
  75. data/lib/jets/resource/replacer.rb +4 -0
  76. data/lib/jets/resource/sns.rb +3 -0
  77. data/lib/jets/resource/standardizer.rb +42 -0
  78. data/lib/jets/router.rb +9 -1
  79. data/lib/jets/rule/dsl.rb +51 -78
  80. data/lib/jets/stack.rb +105 -0
  81. data/lib/jets/stack/builder.rb +38 -0
  82. data/lib/jets/stack/definition.rb +50 -0
  83. data/lib/jets/stack/function.rb +60 -0
  84. data/lib/jets/stack/main.rb +5 -0
  85. data/lib/jets/stack/main/dsl.rb +33 -0
  86. data/lib/jets/stack/main/extensions/base.rb +45 -0
  87. data/lib/jets/stack/main/extensions/cloudwatch.rb +19 -0
  88. data/lib/jets/stack/main/extensions/lambda.rb +69 -0
  89. data/lib/jets/stack/main/extensions/sns.rb +12 -0
  90. data/lib/jets/stack/main/extensions/sqs.rb +8 -0
  91. data/lib/jets/stack/output.rb +38 -0
  92. data/lib/jets/stack/output/dsl.rb +19 -0
  93. data/lib/jets/stack/output/lookup.rb +36 -0
  94. data/lib/jets/stack/parameter.rb +38 -0
  95. data/lib/jets/stack/parameter/dsl.rb +42 -0
  96. data/lib/jets/stack/resource.rb +30 -0
  97. data/lib/jets/stack/resource/dsl.rb +19 -0
  98. data/lib/jets/version.rb +1 -1
  99. metadata +53 -4
  100. data/support/clean +0 -3
  101. data/support/console +0 -3
@@ -1,6 +1,7 @@
1
1
  class Jets::Commands::Console
2
2
  def self.run
3
3
  puts Jets::Booter.message
4
+ Jets.eager_load!
4
5
 
5
6
  # Thanks: https://mutelight.org/bin-console
6
7
  require "irb"
@@ -0,0 +1,17 @@
1
+ ## Examples
2
+
3
+ $ jets runner 'puts "hi"'
4
+ hi
5
+ $ jets runner 'puts Jets.env'
6
+ development
7
+
8
+ Using a script in a file. Let's say you have a script:
9
+
10
+ script.rb:
11
+
12
+ ```ruby
13
+ puts "hello world: #{Jets.env}"
14
+ ```
15
+
16
+ $ jets runner file://script.rb
17
+ hello world: development
@@ -8,6 +8,7 @@ module Jets::Commands
8
8
  desc "build", "Builds and packages project for AWS Lambda"
9
9
  long_desc Help.text(:build)
10
10
  option :templates_only, type: :boolean, default: false, desc: "provide a way to skip building the code and only build the CloudFormation templates"
11
+ option :full, type: :boolean, default: false, desc: "For a fake full build even if the stack does not exist"
11
12
  def build
12
13
  Build.new(options).run
13
14
  end
@@ -56,6 +57,12 @@ module Jets::Commands
56
57
  Console.run
57
58
  end
58
59
 
60
+ desc "runner", "Run Ruby code in the context of Jets app non-interactively"
61
+ long_desc Help.text(:runner)
62
+ def runner(code)
63
+ Runner.run(code)
64
+ end
65
+
59
66
  desc "dbconsole", "Starts DB REPL console"
60
67
  long_desc Help.text(:dbconsole)
61
68
  def dbconsole
@@ -1,5 +1,6 @@
1
1
  module Jets::Commands
2
2
  class New < Sequence
3
+ VALID_MODES = %w[html api job]
3
4
  argument :project_name
4
5
 
5
6
  # Ugly, but when the class_option is only defined in the Thor::Group class
@@ -7,29 +8,33 @@ module Jets::Commands
7
8
  # If anyone knows how to fix this let me know.
8
9
  def self.cli_options
9
10
  [
10
- [:api, type: :boolean, default: false, desc: "API mode."],
11
- [:database, type: :boolean, default: true, desc: "Adds database"],
12
11
  [:bootstrap, type: :boolean, default: true, desc: "Install bootstrap css"], # same option in WebpackerTemplate
12
+ [:database, type: :boolean, default: true, desc: "Adds database"],
13
13
  [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files."],
14
14
  [:git, type: :boolean, default: true, desc: "Git initialize the project"],
15
+ [:mode, default: 'html', desc: "mode: #{VALID_MODES.join(',')}"],
15
16
  [:repo, desc: "GitHub repo to use. Format: user/repo"],
16
17
  [:webpacker, type: :boolean, default: true, desc: "Install webpacker"],
17
18
  ]
18
19
  end
19
20
 
20
21
  cli_options.each do |args|
21
- class_option *args
22
+ class_option(*args)
22
23
  end
23
24
 
24
25
  def set_api_mode
25
26
  # options is a frozen hash by Thor so cannot modify it.
26
27
  # Also had trouble unfreezing it with .dup. So using instance variables instead
27
- if options[:api]
28
+ case options[:mode]
29
+ when 'html'
30
+ @webpacker = options[:webpacker]
31
+ @bootstrap = options[:bootstrap]
32
+ when 'api', 'job'
28
33
  @webpacker = false
29
34
  @bootstrap = false
30
35
  else
31
- @webpacker = options[:webpacker]
32
- @bootstrap = options[:bootstrap]
36
+ puts "Invalid mode provided: #{@options[:mode].colorize(:red)}. Please pass in an valid mode: #{VALID_MODES.join(',').colorize(:green)}."
37
+ exit 1
33
38
  end
34
39
  end
35
40
 
@@ -90,22 +95,37 @@ JS
90
95
  end
91
96
 
92
97
  def user_message
93
- puts <<-EOL
94
- #{"="*64}
95
- Congrats 🎉 You have successfully created a Jets project.
96
-
97
- Cd into the project directory:
98
- cd #{project_name}
98
+ more_info = if options[:mode] == 'job'
99
+ <<~EOL
100
+ Learn more about jobs here: http://rubyonjets.com/docs/jobs/
99
101
 
100
- To start a server and test locally:
101
- jets server # localhost:8888 should have the Jets welcome page
102
+ To deploy to AWS Lambda:
103
+ jets deploy
104
+ EOL
105
+ else
106
+ <<~EOL
107
+ To start a server and test locally:
108
+ jets server # localhost:8888 should have the Jets welcome page
109
+
110
+ Scaffold example:
111
+ jets generate scaffold Post title:string body:text published:boolean
112
+
113
+ To deploy to AWS Lambda, edit your .env.development.remote and add a DATABASE_URL endpoint.
114
+ Then run:
115
+
116
+ jets deploy
117
+ EOL
118
+ end
119
+
120
+ puts <<~EOL
121
+ #{"="*64}
122
+ Congrats 🎉 You have successfully created a Jets project.
102
123
 
103
- Scaffold example:
104
- jets generate scaffold Post title:string body:text published:boolean
124
+ Cd into the project directory:
125
+ cd #{project_name}
105
126
 
106
- To deploy to AWS Lambda:
107
- jets deploy
108
- EOL
127
+ #{more_info}
128
+ EOL
109
129
  end
110
130
  end
111
131
  end
@@ -0,0 +1,18 @@
1
+ class Jets::Commands::Runner
2
+ def self.run(code)
3
+ Jets.eager_load!
4
+
5
+ if code =~ %r{^file://}
6
+ path = code.sub('file://', '')
7
+ full_path = "#{Jets.root}#{path}"
8
+ if File.exist?(full_path)
9
+ code = IO.read(full_path)
10
+ else
11
+ puts "ERROR: file not found at #{full_path}".colorize(:red)
12
+ exit 1
13
+ end
14
+ end
15
+
16
+ eval(code) # inline script
17
+ end
18
+ end
@@ -35,7 +35,33 @@ private
35
35
 
36
36
  def copy_project
37
37
  puts "Creating new project called #{project_name}."
38
- directory ".", project_name
38
+ directory ".", project_name, copy_options
39
+ end
40
+
41
+ def copy_options
42
+ # list of words to include in the exclude pattern and will not be generated
43
+ words = %w[
44
+ Procfile
45
+ controllers
46
+ helpers
47
+ javascript
48
+ models/application_
49
+ views
50
+ config.ru
51
+ database.yml
52
+ dynamodb.yml
53
+ routes
54
+ db/
55
+ spec
56
+ yarn
57
+ public
58
+ ]
59
+
60
+ if @options[:mode] == 'job'
61
+ { exclude_pattern: Regexp.new(words.join('|')) }
62
+ else
63
+ {}
64
+ end
39
65
  end
40
66
 
41
67
  def git_installed?
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
@@ -10,7 +10,6 @@ gem "webpacker", git: "https://github.com/tongueroo/webpacker.git", branch: "jet
10
10
  # Include pg gem if you are using ActiveRecord, remove if you are not
11
11
  gem "pg", "~> 0.21"
12
12
  <% end %>
13
- gem "mimemagic"
14
13
 
15
14
  group :development, :test do
16
15
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
@@ -1,2 +1,4 @@
1
1
  class ApplicationJob < Jets::Job::Base
2
+ # Adjust to increase the default timeout for all Job classes
3
+ class_timeout 30
2
4
  end
@@ -1,6 +1,7 @@
1
1
  Jets.application.configure do
2
2
  config.project_name = "<%= project_name %>"
3
- config.api_generator = <%= !!@options[:api] %>
3
+ config.api_generator = <%= @options[:mode] == 'api' %> # deprecated
4
+ config.api_mode = <%= @options[:mode] == 'api' %>
4
5
 
5
6
  # config.prewarm.enable = true # default is true
6
7
  # config.prewarm.rate = '30 minutes' # default is '30 minutes'
@@ -1,5 +1,9 @@
1
1
  Jets.application.routes.draw do
2
- # Default homepage. This should be replaced.
2
+ # Default homepage. This should be replaced. Replacing requires using JETS_ENV_EXTRA
3
+ # or deleting and deploying twice.
4
+ # More info:
5
+ # http://rubyonjets.com/docs/routes-workaround/
6
+ # http://rubyonjets.com/docs/env-extra/
3
7
  root "jets/welcome#index"
4
8
 
5
9
  # Required for API Gateway to serve static utf8 content out of public folder.
@@ -1,12 +1,13 @@
1
- ENV["JETS_ENV"] ||= "test"
2
1
  ENV["TEST"] = "1"
3
- abort("The Jets environment is running in production mode!") if Jets::Config.env == "production"
2
+ ENV["JETS_ENV"] ||= "test"
3
+ # Ensures aws api never called. Fixture home folder does not contain ~/.aws/credentails
4
+ ENV['HOME'] = "spec/fixtures/home"
4
5
 
5
- require "pp"
6
6
  require "byebug"
7
7
  require "fileutils"
8
-
9
8
  require "jets"
9
+
10
+ abort("The Jets environment is running in production mode!") if Jets.env == "production"
10
11
  Jets.boot
11
12
 
12
13
  <% if @webpacker -%>
@@ -90,7 +90,6 @@ module Jets::Core
90
90
  def eager_load_jets
91
91
  lib_jets = File.expand_path(".", File.dirname(__FILE__))
92
92
  Dir.glob("#{lib_jets}/**/*.rb").select do |path|
93
- # puts "path #{path}"
94
93
  next if !File.file?(path)
95
94
  next if skip_eager_load_paths?(path)
96
95
 
@@ -115,7 +114,8 @@ module Jets::Core
115
114
  path =~ %r{/internal/app} ||
116
115
  path =~ %r{/webpacker} ||
117
116
  path =~ %r{/cli} ||
118
- path =~ %r{/core_ext}
117
+ path =~ %r{/core_ext} ||
118
+ path =~ %r{/jets/stack}
119
119
  end
120
120
 
121
121
  def class_mappings(class_name)
@@ -129,13 +129,15 @@ module Jets::Core
129
129
  def eager_load_app
130
130
  Dir.glob("#{Jets.root}app/**/*.rb").select do |path|
131
131
  next if !File.file?(path) or path =~ %r{/javascript/} or path =~ %r{/views/}
132
+ next if path.include?('app/shared/functions')
132
133
 
133
134
  class_name = path
134
135
  .sub(/\.rb$/,'') # remove .rb
135
- .sub(/^\.\//,'') # remove ./
136
- .sub(/app\/\w+\//,'') # remove app/controllers or app/jobs etc
137
- .classify
138
- # puts "eager_load! loading path: #{path} class_name: #{class_name}" if ENV['JETS_DEBUG']
136
+ .sub(%{^\./},'') # remove ./
137
+ .sub(Jets.root.to_s,'')
138
+ .sub(%r{app/shared/\w+/},'') # remove shared/resources or shared/extensions
139
+ .sub(%r{app/\w+/},'') # remove app/controllers or app/jobs etc
140
+ class_name = class_name.classify
139
141
  class_name.constantize # use constantize instead of require so dont have to worry about order.
140
142
  end
141
143
  end
@@ -9,6 +9,7 @@ Jets.application.configure do
9
9
  app/jobs
10
10
  app/rules
11
11
  app/helpers
12
+ app/shared/resources
12
13
  ]
13
14
  config.extra_autoload_paths = []
14
15
 
@@ -27,7 +27,7 @@ class Jets::Generator
27
27
  g.stylesheets false
28
28
  g.javascripts false
29
29
  g.assets false
30
- g.api Jets.config.api_generator
30
+ g.api Jets.config.api_mode
31
31
  g.resource_route true
32
32
  g.templates.unshift(template_paths)
33
33
  g
@@ -0,0 +1,23 @@
1
+ module Jets
2
+ class Inflections
3
+ class << self
4
+ def load!
5
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
6
+ base_inflections.each do |k,v|
7
+ inflect.irregular k,v
8
+ end
9
+ # Users can add custom inflections
10
+ Jets.config.inflections.irregular.each do |k,v|
11
+ inflect.irregular k,v
12
+ end
13
+ end
14
+ end
15
+
16
+ def base_inflections
17
+ {
18
+ sns: 'sns'
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,7 +3,9 @@
3
3
  # So the Jets::Job::Dsl overrides some of the Jets::Lambda::Functions behavior.
4
4
  #
5
5
  # Implements:
6
- # default_associated_resource: must return @resources
6
+ #
7
+ # default_associated_resource_definition
8
+ #
7
9
  module Jets::Job::Dsl
8
10
  extend ActiveSupport::Concern
9
11
 
@@ -11,75 +13,95 @@ module Jets::Job::Dsl
11
13
  class << self
12
14
  # Public: Creates CloudWatch Event Rule
13
15
  #
14
- # expression - The rate expression.
16
+ # expression - The rate expression.
15
17
  #
16
18
  # Examples
17
19
  #
18
20
  # rate("10 minutes")
19
21
  # rate("10 minutes", description: "Hard job")
20
22
  #
21
- def rate(expression)
22
- update_properties(schedule_expression: "rate(#{expression})")
23
+ def rate(expression, props={})
24
+ schedule_job("rate(#{expression})", props)
23
25
  end
24
26
 
25
- def cron(expression)
26
- update_properties(schedule_expression: "cron(#{expression})")
27
+ # Public: Creates CloudWatch Event Rule
28
+ #
29
+ # expression - The cron expression.
30
+ #
31
+ # Examples
32
+ #
33
+ # cron("0 */12 * * ? *")
34
+ # cron("0 */12 * * ? *", description: "Hard job")
35
+ #
36
+ def cron(expression, props={})
37
+ schedule_job("cron(#{expression})", props)
27
38
  end
28
39
 
29
- def event_pattern(details={})
30
- event_rule(event_pattern: details)
40
+ def schedule_job(expression, props={})
41
+ @associated_properties = nil # dont use any current associated_properties
42
+ props = props.merge(schedule_expression: expression)
43
+ associated_properties(props)
44
+ # Eager define resource
45
+ resource(events_rule_definition) # add associated resources immediately
46
+ @associated_properties = nil # reset for next definition, since we're defining eagerly
47
+ end
48
+
49
+ def event_pattern(details={}, props={})
50
+ @associated_properties = nil # dont use any current associated_properties
51
+ props = props.merge(event_pattern: details)
52
+ associated_properties(props)
53
+ # Eager define resource
54
+ resource(events_rule_definition) # add associated resources immediately
55
+ @associated_properties = nil # reset for next definition, since we're defining eagerly
31
56
  add_descriptions # useful: generic description in the Event Rule console
32
57
  end
33
58
 
59
+ def events_rule(props={})
60
+ @associated_properties = nil # dont use any current associated_properties
61
+ associated_properties(props)
62
+ # Eager define resource
63
+ resource(events_rule_definition) # add associated resources immediately
64
+ @associated_properties = nil # reset for next definition, since we're defining eagerly
65
+ end
66
+
67
+ # Works with eager definitions
34
68
  def add_descriptions
35
69
  numbered_resources = []
36
70
  n = 1
37
- @resources.map do |definition|
38
- logical_id = definition.keys.first
39
- attributes = definition.values.first
40
- attributes[:properties][:description] = "#{self.name} Event Rule #{n}"
41
- numbered_resources << { "#{logical_id}" => attributes }
71
+ @associated_resources.map do |associated|
72
+ # definition = associated.definition
73
+ # puts "associated #{associated.inspect}"
74
+ # puts "definition #{definition.inspect}"
75
+
76
+ # logical_id = definition.keys.first
77
+ # attributes = definition.values.first
78
+
79
+ logical_id = associated.logical_id
80
+ attributes = associated.attributes
81
+
82
+ attributes[:properties][:description] ||= "#{self.name} Event Rule #{n}"
83
+ new_definition = { "#{logical_id}" => attributes }
84
+ numbered_resources << Jets::Resource::Associated.new(new_definition)
42
85
  n += 1
43
86
  end
44
- @resources = numbered_resources
45
- end
46
-
47
- def default_associated_resource
48
- event_rule
49
- @resources # must return @resoures for update_properties
87
+ @associated_resources = numbered_resources
50
88
  end
51
89
 
52
- def event_rule(props={})
53
- default_props = {
54
- state: "ENABLED",
55
- targets: [{
56
- arn: "!GetAtt {namespace}LambdaFunction.Arn",
57
- id: "{namespace}RuleTarget"
58
- }]
59
- }
60
- properties = default_props.deep_merge(props)
90
+ ASSOCIATED_PROPERTIES = %W[
91
+ description
92
+ state
93
+ schedule_expression
94
+ ]
95
+ define_associated_properties(ASSOCIATED_PROPERTIES)
96
+ alias_method :desc, :description
61
97
 
62
- resource("{namespace}EventsRule" => {
63
- type: "AWS::Events::Rule",
64
- properties: properties
65
- })
66
-
67
- add_logical_id_counter if @resources.size > 1
98
+ def default_associated_resource_definition(meth)
99
+ events_rule_definition
68
100
  end
69
101
 
70
- # Loop back through the resources and add a counter to the end of the id
71
- # to handle multiple events.
72
- # Then replace @resources entirely
73
- def add_logical_id_counter
74
- numbered_resources = []
75
- n = 1
76
- @resources.map do |definition|
77
- logical_id = definition.keys.first
78
- logical_id = logical_id.sub(/\d+$/,'')
79
- numbered_resources << { "#{logical_id}#{n}" => definition.values.first }
80
- n += 1
81
- end
82
- @resources = numbered_resources
102
+ def events_rule_definition
103
+ resource = Jets::Resource::Events::Rule.new(associated_properties)
104
+ resource.definition # returns a definition to be added by associated_resources
83
105
  end
84
106
  end
85
107
  end