pennyworth 11.2.1 → 12.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -2
  3. data/LICENSE.adoc +207 -155
  4. data/README.adoc +28 -40
  5. data/{bin → exe}/pennyworth +0 -0
  6. data/lib/pennyworth/cli/actions/config.rb +1 -1
  7. data/lib/pennyworth/cli/actions/encodings.rb +1 -1
  8. data/lib/pennyworth/cli/actions/git_hub.rb +1 -1
  9. data/lib/pennyworth/cli/actions/http_statuses.rb +1 -1
  10. data/lib/pennyworth/cli/actions/ruby_gems.rb +1 -1
  11. data/lib/pennyworth/cli/actions/system/errors.rb +1 -1
  12. data/lib/pennyworth/cli/actions/system/signals.rb +1 -1
  13. data/lib/pennyworth/cli/actions/text.rb +1 -1
  14. data/lib/pennyworth/cli/parser.rb +33 -0
  15. data/lib/pennyworth/cli/parsers/core.rb +4 -3
  16. data/lib/pennyworth/cli/parsers/git_hub.rb +3 -2
  17. data/lib/pennyworth/cli/parsers/ruby_gems.rb +3 -2
  18. data/lib/pennyworth/cli/shell.rb +3 -3
  19. data/lib/pennyworth/configuration/content.rb +33 -0
  20. data/lib/pennyworth/{cli/configuration → configuration}/defaults.yml +0 -0
  21. data/lib/pennyworth/configuration/loader.rb +35 -0
  22. data/lib/pennyworth/container.rb +1 -1
  23. data/lib/pennyworth/identity.rb +1 -1
  24. data/lib/pennyworth/inflector.rb +1 -1
  25. data/lib/pennyworth/integrations/git_hub/client.rb +1 -1
  26. data/lib/pennyworth/loaders/http_statuses.rb +2 -2
  27. data/lib/pennyworth/loaders/system/signals.rb +1 -1
  28. data/lib/pennyworth/presenters/gem.rb +6 -1
  29. data/lib/pennyworth/presenters/repository.rb +6 -1
  30. data/lib/pennyworth/serializers/project.rb +2 -2
  31. data.tar.gz.sig +0 -0
  32. metadata +17 -17
  33. metadata.gz.sig +0 -0
  34. data/lib/pennyworth/cli/configuration/content.rb +0 -28
  35. data/lib/pennyworth/cli/configuration/loader.rb +0 -37
  36. data/lib/pennyworth/cli/parsers/assembler.rb +0 -32
  37. data/lib/pennyworth/cli/parsers.rb +0 -11
@@ -5,7 +5,7 @@ module Pennyworth
5
5
  module Actions
6
6
  # Handles the configuration action.
7
7
  class Config
8
- def initialize configuration: Configuration::Loader::HANDLER, container: Container
8
+ def initialize configuration: Configuration::Loader::CLIENT, container: Container
9
9
  @configuration = configuration
10
10
  @container = container
11
11
  end
@@ -10,7 +10,7 @@ module Pennyworth
10
10
  @container = container
11
11
  end
12
12
 
13
- def call = processor.call.to_json.then { |json| logger.info json }
13
+ def call = processor.call.to_json.then { |json| logger.info { json } }
14
14
 
15
15
  private
16
16
 
@@ -13,7 +13,7 @@ module Pennyworth
13
13
  def call endpoint
14
14
  processor.call(endpoint)
15
15
  .to_json
16
- .then { |json| logger.info json }
16
+ .then { |json| logger.info { json } }
17
17
  end
18
18
 
19
19
  private
@@ -10,7 +10,7 @@ module Pennyworth
10
10
  @container = container
11
11
  end
12
12
 
13
- def call = processor.call.to_json.then { |json| logger.info json }
13
+ def call = processor.call.to_json.then { |json| logger.info { json } }
14
14
 
15
15
  private
16
16
 
@@ -13,7 +13,7 @@ module Pennyworth
13
13
  def call endpoint
14
14
  processor.call(endpoint)
15
15
  .to_json
16
- .then { |json| logger.info json }
16
+ .then { |json| logger.info { json } }
17
17
  end
18
18
 
19
19
  private
@@ -11,7 +11,7 @@ module Pennyworth
11
11
  @container = container
12
12
  end
13
13
 
14
- def call = processor.call.to_json.then { |json| logger.info json }
14
+ def call = processor.call.to_json.then { |json| logger.info { json } }
15
15
 
16
16
  private
17
17
 
@@ -11,7 +11,7 @@ module Pennyworth
11
11
  @container = container
12
12
  end
13
13
 
14
- def call = processor.call.to_json.then { |json| logger.info json }
14
+ def call = processor.call.to_json.then { |json| logger.info { json } }
15
15
 
16
16
  private
17
17
 
@@ -10,7 +10,7 @@ module Pennyworth
10
10
  @container = container
11
11
  end
12
12
 
13
- def call(content) = processor.call(content).to_json.then { |json| logger.info json }
13
+ def call(content) = processor.call(content).to_json.then { |json| logger.info { json } }
14
14
 
15
15
  private
16
16
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module Pennyworth
6
+ module CLI
7
+ # Assembles and parses all Command Line Interface (CLI) options.
8
+ class Parser
9
+ CLIENT = OptionParser.new nil, 40, " "
10
+ SECTIONS = [Parsers::Core, Parsers::GitHub, Parsers::RubyGems].freeze # Order is important.
11
+
12
+ def initialize configuration = Configuration::Loader.call,
13
+ sections: SECTIONS,
14
+ client: CLIENT
15
+ @configuration = configuration.dup
16
+ @sections = sections
17
+ @client = client
18
+ end
19
+
20
+ def call arguments = []
21
+ sections.each { |parser| parser.call configuration, client: }
22
+ client.parse arguments
23
+ configuration.freeze
24
+ end
25
+
26
+ def to_s = client.to_s
27
+
28
+ private
29
+
30
+ attr_reader :configuration, :client, :sections
31
+ end
32
+ end
33
+ end
@@ -12,7 +12,7 @@ module Pennyworth
12
12
  class Core
13
13
  def self.call(...) = new(...).call
14
14
 
15
- def initialize configuration = Configuration::Loader.call, client: CLIENT
15
+ def initialize configuration = Configuration::Loader.call, client: Parser::CLIENT
16
16
  @configuration = configuration
17
17
  @client = client
18
18
  end
@@ -21,7 +21,8 @@ module Pennyworth
21
21
  client.banner = "#{Identity::LABEL} - #{Identity::SUMMARY}"
22
22
  client.separator "\nUSAGE:\n"
23
23
  collate
24
- arguments.empty? ? arguments : client.parse!(arguments)
24
+ client.parse arguments
25
+ configuration
25
26
  end
26
27
 
27
28
  private
@@ -85,7 +86,7 @@ module Pennyworth
85
86
 
86
87
  def add_version
87
88
  client.on "-v", "--version", "Show gem version." do
88
- configuration.action_version = Identity::VERSION_LABEL
89
+ configuration.action_version = true
89
90
  end
90
91
  end
91
92
 
@@ -11,7 +11,7 @@ module Pennyworth
11
11
 
12
12
  def self.call(...) = new(...).call
13
13
 
14
- def initialize configuration = Configuration::Loader.call, client: CLIENT
14
+ def initialize configuration = Configuration::Loader.call, client: Parser::CLIENT
15
15
  @configuration = configuration
16
16
  @client = client
17
17
  end
@@ -19,7 +19,8 @@ module Pennyworth
19
19
  def call arguments = []
20
20
  client.separator "\nGITHUB OPTIONS:\n"
21
21
  private_methods.sort.grep(/add_/).each { |method| __send__ method }
22
- arguments.empty? ? arguments : client.parse!(arguments)
22
+ client.parse arguments
23
+ configuration
23
24
  end
24
25
 
25
26
  private
@@ -7,7 +7,7 @@ module Pennyworth
7
7
  class RubyGems
8
8
  def self.call(...) = new(...).call
9
9
 
10
- def initialize configuration = Configuration::Loader.call, client: CLIENT
10
+ def initialize configuration = Configuration::Loader.call, client: Parser::CLIENT
11
11
  @configuration = configuration
12
12
  @client = client
13
13
  end
@@ -15,7 +15,8 @@ module Pennyworth
15
15
  def call arguments = []
16
16
  client.separator "\nRUBYGEMS OPTIONS:\n"
17
17
  add_owner
18
- arguments.empty? ? arguments : client.parse!(arguments)
18
+ client.parse arguments
19
+ configuration
19
20
  end
20
21
 
21
22
  private
@@ -15,7 +15,7 @@ module Pennyworth
15
15
  text: Actions::Text.new
16
16
  }.freeze
17
17
 
18
- def initialize parser: Parsers::Assembler.new, actions: ACTIONS, container: Container
18
+ def initialize parser: Parser.new, actions: ACTIONS, container: Container
19
19
  @parser = parser
20
20
  @actions = actions
21
21
  @container = container
@@ -24,7 +24,7 @@ module Pennyworth
24
24
  def call arguments = []
25
25
  perform parser.call(arguments)
26
26
  rescue OptionParser::ParseError, KeyError => error
27
- logger.error error.message
27
+ logger.error { error.message }
28
28
  end
29
29
 
30
30
  private
@@ -43,7 +43,7 @@ module Pennyworth
43
43
  in action_system_signals: true then system_signals
44
44
  in action_system_errors: true then system_errors
45
45
  in action_text: String => content then text content
46
- in action_version: String => version then logger.info version
46
+ in action_version: true then logger.info { Identity::VERSION_LABEL }
47
47
  else usage
48
48
  end
49
49
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pennyworth
4
+ module Configuration
5
+ # Defines configuration content as the primary source of truth for use throughout the gem.
6
+ Content = Struct.new(
7
+ :action_encodings,
8
+ :action_git_hub,
9
+ :action_http_statuses,
10
+ :action_ruby_gems,
11
+ :action_system_errors,
12
+ :action_system_signals,
13
+ :action_text,
14
+ :action_config,
15
+ :action_version,
16
+ :action_help,
17
+ :alfred_preferences,
18
+ :inflections,
19
+ :git_hub_api_url,
20
+ :git_hub_organization,
21
+ :git_hub_user,
22
+ :http_statuses_url,
23
+ :ruby_gems_api_url,
24
+ :ruby_gems_owner,
25
+ keyword_init: true
26
+ ) do
27
+ def initialize *arguments
28
+ super
29
+ freeze
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "refinements/hashes"
5
+ require "refinements/structs"
6
+ require "runcom"
7
+ require "yaml"
8
+
9
+ module Pennyworth
10
+ module Configuration
11
+ # Represents the fully assembled Command Line Interface (CLI) configuration.
12
+ class Loader
13
+ using Refinements::Hashes
14
+ using Refinements::Structs
15
+
16
+ DEFAULTS = YAML.load_file(Pathname(__dir__).join("defaults.yml")).freeze
17
+ CLIENT = Runcom::Config.new "#{Identity::NAME}/configuration.yml", defaults: DEFAULTS
18
+
19
+ def self.call = new.call
20
+
21
+ def self.with_defaults = new(client: DEFAULTS)
22
+
23
+ def initialize content: Content.new, client: CLIENT
24
+ @content = content
25
+ @client = client
26
+ end
27
+
28
+ def call = content.merge(**client.to_h.flatten_keys)
29
+
30
+ private
31
+
32
+ attr_reader :content, :client
33
+ end
34
+ end
35
+ end
@@ -10,7 +10,7 @@ module Pennyworth
10
10
  module Container
11
11
  extend Dry::Container::Mixin
12
12
 
13
- register(:configuration) { CLI::Configuration::Loader.call }
13
+ register(:configuration) { Configuration::Loader.call }
14
14
  register(:environment) { ENV }
15
15
  register(:kernel) { Kernel }
16
16
  register(:http) { HTTP }
@@ -6,7 +6,7 @@ module Pennyworth
6
6
  NAME = "pennyworth"
7
7
  LABEL = "Pennyworth"
8
8
  SUMMARY = "A command line interface that augments Alfred workflows."
9
- VERSION = "11.2.1"
9
+ VERSION = "12.0.2"
10
10
  VERSION_LABEL = "#{LABEL} #{VERSION}".freeze
11
11
  end
12
12
  end
@@ -3,7 +3,7 @@
3
3
  module Pennyworth
4
4
  # Overrides any string to desired form if matched, otherwise answers the original string.
5
5
  class Inflector
6
- DEFAULTS = Array(CLI::Configuration::Loader.call.inflections).reduce({}, :merge)
6
+ DEFAULTS = Array(Configuration::Loader.call.inflections).reduce({}, :merge)
7
7
 
8
8
  def initialize overrides = DEFAULTS
9
9
  @overrides = overrides
@@ -20,7 +20,7 @@ module Pennyworth
20
20
  end
21
21
 
22
22
  def get endpoint, parameters: {}
23
- paginate ->(page) { sole_get endpoint, parameters: parameters.merge(page: page) }
23
+ paginate ->(page) { sole_get endpoint, parameters: parameters.merge(page:) }
24
24
  end
25
25
 
26
26
  private
@@ -8,14 +8,14 @@ module Pennyworth
8
8
  class HTTPStatuses
9
9
  def initialize codes: Rack::Utils::HTTP_STATUS_CODES,
10
10
  model: Models::HTTPStatus,
11
- configuration: CLI::Configuration::Loader.call
11
+ configuration: Configuration::Loader.call
12
12
  @codes = codes
13
13
  @model = model
14
14
  @configuration = configuration
15
15
  end
16
16
 
17
17
  def call _omit = nil
18
- codes.map { |(code, label)| model[code: code, label: label, url: "#{url}/#{code}"] }
18
+ codes.map { |(code, label)| model[code:, label:, url: "#{url}/#{code}"] }
19
19
  end
20
20
 
21
21
  private
@@ -10,7 +10,7 @@ module Pennyworth
10
10
  @model = model
11
11
  end
12
12
 
13
- def call(_omit = nil) = list.map { |name, number| model[number: number, name: name] }
13
+ def call(_omit = nil) = list.map { |name, number| model[number:, name:] }
14
14
 
15
15
  private
16
16
 
@@ -34,10 +34,15 @@ module Pennyworth
34
34
 
35
35
  def source_url = record.fetch(:source_code_uri)
36
36
 
37
- def changes_url = record.fetch(:changelog_uri)
37
+ def changes_url
38
+ warn "[DEPRECATION]: #changes_url is deprecated, use #versions_url instead."
39
+ versions_url
40
+ end
38
41
 
39
42
  def issues_url = record.fetch(:bug_tracker_uri)
40
43
 
44
+ def versions_url = record.fetch(:changelog_uri)
45
+
41
46
  def updated_at = record.fetch(:version_created_at)
42
47
 
43
48
  private
@@ -23,10 +23,15 @@ module Pennyworth
23
23
 
24
24
  def source_url = record.fetch(:html_url)
25
25
 
26
- def changes_url = "#{site_url}/changes.html"
26
+ def changes_url
27
+ warn "[DEPRECATION]: #changes_url is deprecated, use #versions_url instead."
28
+ versions_url
29
+ end
27
30
 
28
31
  def issues_url = "#{source_url}/issues"
29
32
 
33
+ def versions_url = "#{site_url}/versions"
34
+
30
35
  private
31
36
 
32
37
  attr_reader :record, :inflector
@@ -26,9 +26,9 @@ module Pennyworth
26
26
 
27
27
  def modifications
28
28
  {
29
- control: modification(presenter.changes_url, "View changes."),
30
29
  alt: modification(presenter.source_url, "View source."),
31
- cmd: modification(presenter.issues_url, "View issues.")
30
+ cmd: modification(presenter.issues_url, "View issues."),
31
+ control: modification(presenter.versions_url, "View versions.")
32
32
  }
33
33
  end
34
34
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pennyworth
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.2.1
4
+ version: 12.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain:
11
11
  - |
12
12
  -----BEGIN CERTIFICATE-----
@@ -28,7 +28,7 @@ cert_chain:
28
28
  lkHilIrX69jq8wMPpBhlaw2mRmeSL50Wv5u6xVBvOHhXFSP1crXM95vfLhLyRYod
29
29
  W2A=
30
30
  -----END CERTIFICATE-----
31
- date: 2021-10-21 00:00:00.000000000 Z
31
+ date: 2022-01-09 00:00:00.000000000 Z
32
32
  dependencies:
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: dry-container
@@ -92,28 +92,28 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '8.5'
95
+ version: '9.0'
96
96
  type: :runtime
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '8.5'
102
+ version: '9.0'
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: runcom
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '7.0'
109
+ version: '8.0'
110
110
  type: :runtime
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '7.0'
116
+ version: '8.0'
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: zeitwerk
119
119
  requirement: !ruby/object:Gem::Requirement
@@ -140,7 +140,7 @@ extra_rdoc_files:
140
140
  files:
141
141
  - LICENSE.adoc
142
142
  - README.adoc
143
- - bin/pennyworth
143
+ - exe/pennyworth
144
144
  - lib/pennyworth.rb
145
145
  - lib/pennyworth/cli/actions/config.rb
146
146
  - lib/pennyworth/cli/actions/encodings.rb
@@ -150,15 +150,14 @@ files:
150
150
  - lib/pennyworth/cli/actions/system/errors.rb
151
151
  - lib/pennyworth/cli/actions/system/signals.rb
152
152
  - lib/pennyworth/cli/actions/text.rb
153
- - lib/pennyworth/cli/configuration/content.rb
154
- - lib/pennyworth/cli/configuration/defaults.yml
155
- - lib/pennyworth/cli/configuration/loader.rb
156
- - lib/pennyworth/cli/parsers.rb
157
- - lib/pennyworth/cli/parsers/assembler.rb
153
+ - lib/pennyworth/cli/parser.rb
158
154
  - lib/pennyworth/cli/parsers/core.rb
159
155
  - lib/pennyworth/cli/parsers/git_hub.rb
160
156
  - lib/pennyworth/cli/parsers/ruby_gems.rb
161
157
  - lib/pennyworth/cli/shell.rb
158
+ - lib/pennyworth/configuration/content.rb
159
+ - lib/pennyworth/configuration/defaults.yml
160
+ - lib/pennyworth/configuration/loader.rb
162
161
  - lib/pennyworth/container.rb
163
162
  - lib/pennyworth/identity.rb
164
163
  - lib/pennyworth/inflector.rb
@@ -192,11 +191,12 @@ files:
192
191
  - lib/pennyworth/serializers/text.rb
193
192
  homepage: https://www.alchemists.io/projects/pennyworth
194
193
  licenses:
195
- - Apache-2.0
194
+ - Hippocratic-3.0
196
195
  metadata:
197
196
  bug_tracker_uri: https://github.com/bkuhlmann/pennyworth/issues
198
- changelog_uri: https://www.alchemists.io/projects/pennyworth/changes.html
197
+ changelog_uri: https://www.alchemists.io/projects/pennyworth/versions
199
198
  documentation_uri: https://www.alchemists.io/projects/pennyworth
199
+ rubygems_mfa_required: 'true'
200
200
  source_code_uri: https://github.com/bkuhlmann/pennyworth
201
201
  post_install_message:
202
202
  rdoc_options: []
@@ -206,14 +206,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
206
206
  requirements:
207
207
  - - "~>"
208
208
  - !ruby/object:Gem::Version
209
- version: '3.0'
209
+ version: '3.1'
210
210
  required_rubygems_version: !ruby/object:Gem::Requirement
211
211
  requirements:
212
212
  - - ">="
213
213
  - !ruby/object:Gem::Version
214
214
  version: '0'
215
215
  requirements: []
216
- rubygems_version: 3.2.29
216
+ rubygems_version: 3.3.4
217
217
  signing_key:
218
218
  specification_version: 4
219
219
  summary: A command line interface that augments Alfred workflows.
metadata.gz.sig CHANGED
Binary file
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Pennyworth
4
- module CLI
5
- module Configuration
6
- # Defines configuration content as the primary source of truth for use throughout the gem.
7
- Content = Struct.new :action_encodings,
8
- :action_git_hub,
9
- :action_http_statuses,
10
- :action_ruby_gems,
11
- :action_system_errors,
12
- :action_system_signals,
13
- :action_text,
14
- :action_config,
15
- :action_version,
16
- :action_help,
17
- :alfred_preferences,
18
- :inflections,
19
- :git_hub_api_url,
20
- :git_hub_organization,
21
- :git_hub_user,
22
- :http_statuses_url,
23
- :ruby_gems_api_url,
24
- :ruby_gems_owner,
25
- keyword_init: true
26
- end
27
- end
28
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
- require "refinements/hashes"
5
- require "refinements/structs"
6
- require "runcom"
7
- require "yaml"
8
-
9
- module Pennyworth
10
- module CLI
11
- module Configuration
12
- # Represents the fully assembled Command Line Interface (CLI) configuration.
13
- class Loader
14
- using Refinements::Hashes
15
- using Refinements::Structs
16
-
17
- DEFAULTS = YAML.load_file(Pathname(__dir__).join("defaults.yml")).freeze
18
- HANDLER = Runcom::Config.new "#{Identity::NAME}/configuration.yml", defaults: DEFAULTS
19
-
20
- def self.call = new.call
21
-
22
- def self.with_defaults = new(handler: DEFAULTS)
23
-
24
- def initialize content: Content.new, handler: HANDLER
25
- @content = content
26
- @handler = handler
27
- end
28
-
29
- def call = content.merge(**handler.to_h.flatten_keys)
30
-
31
- private
32
-
33
- attr_reader :content, :handler
34
- end
35
- end
36
- end
37
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Pennyworth
4
- module CLI
5
- module Parsers
6
- SECTIONS = [Core, GitHub, RubyGems].freeze # Order is important.
7
-
8
- # Assembles and parses all Command Line Interface (CLI) options.
9
- class Assembler
10
- def initialize configuration = CLI::Configuration::Loader.call,
11
- sections: SECTIONS,
12
- client: CLIENT
13
- @configuration = configuration
14
- @sections = sections
15
- @client = client
16
- end
17
-
18
- def call arguments = []
19
- sections.each { |parser| parser.call configuration, client: client }
20
- client.parse! arguments
21
- configuration
22
- end
23
-
24
- def to_s = client.to_s
25
-
26
- private
27
-
28
- attr_reader :configuration, :client, :sections
29
- end
30
- end
31
- end
32
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "optparse"
4
-
5
- module Pennyworth
6
- module CLI
7
- module Parsers
8
- CLIENT = OptionParser.new nil, 40, " "
9
- end
10
- end
11
- end