ruby-nginx 1.0.0.pre.alpha → 1.0.0.pre.beta.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +136 -23
  3. data/lib/ruby/nginx/cli.rb +4 -5
  4. data/lib/ruby/nginx/commands/add_host_mapping.rb +30 -0
  5. data/lib/ruby/nginx/commands/add_nginx_config.rb +18 -0
  6. data/lib/ruby/nginx/commands/create_mkcert_certificate.rb +18 -0
  7. data/lib/ruby/nginx/commands/install_mkcert.rb +65 -0
  8. data/lib/ruby/nginx/commands/install_nginx.rb +56 -0
  9. data/lib/ruby/nginx/commands/nginx_options.rb +33 -0
  10. data/lib/ruby/nginx/commands/nginx_version.rb +22 -0
  11. data/lib/ruby/nginx/commands/remove_host_mapping.rb +47 -0
  12. data/lib/ruby/nginx/commands/remove_nginx_config.rb +18 -0
  13. data/lib/ruby/nginx/commands/setup_mkcert.rb +15 -0
  14. data/lib/ruby/nginx/commands/setup_nginx.rb +75 -0
  15. data/lib/ruby/nginx/commands/start_nginx.rb +28 -0
  16. data/lib/ruby/nginx/commands/stop_nginx.rb +31 -0
  17. data/lib/ruby/nginx/commands/terminal_command.rb +51 -0
  18. data/lib/ruby/nginx/commands/validate_nginx_config.rb +28 -0
  19. data/lib/ruby/nginx/configuration.rb +20 -7
  20. data/lib/ruby/nginx/constants.rb +10 -0
  21. data/lib/ruby/nginx/exceptions.rb +16 -0
  22. data/lib/ruby/nginx/system/hosts.rb +23 -0
  23. data/lib/ruby/nginx/system/mkcert.rb +27 -0
  24. data/lib/ruby/nginx/system/nginx.rb +62 -0
  25. data/lib/ruby/nginx/system/os.rb +48 -0
  26. data/lib/ruby/nginx/{utils → system}/safe_file.rb +10 -4
  27. data/lib/ruby/nginx/templates/nginx.conf.erb +4 -0
  28. data/lib/ruby/nginx/version.rb +3 -1
  29. data/lib/ruby/nginx.rb +24 -9
  30. metadata +57 -13
  31. data/lib/ruby/nginx/utils/command.rb +0 -29
  32. data/lib/ruby/nginx/utils/mkcert.rb +0 -28
  33. data/lib/ruby/nginx/utils/nginx.rb +0 -63
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "terminal_command"
4
+
5
+ module Ruby
6
+ module Nginx
7
+ module Commands
8
+ class StartNginx < TerminalCommand
9
+ def initialize(sudo: false)
10
+ @sudo = sudo
11
+ sudo_reason = "Allow sudo elevation to start NGINX?"
12
+ cmd = sudoify("nginx", sudo, sudo_reason)
13
+
14
+ super(cmd:, raise: Ruby::Nginx::StartError)
15
+ end
16
+
17
+ def run
18
+ super
19
+ rescue Ruby::Nginx::StartError
20
+ raise if @sudo
21
+
22
+ # Elevate to sudo and try again.
23
+ self.class.new(sudo: true).run
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "terminal_command"
4
+
5
+ module Ruby
6
+ module Nginx
7
+ module Commands
8
+ class StopNginx < TerminalCommand
9
+ def initialize(sudo: false)
10
+ @sudo = sudo
11
+ sudo_reason = "Allow sudo elevation to stop NGINX?"
12
+ cmd = sudoify("nginx -s stop", sudo, sudo_reason)
13
+
14
+ super(cmd:, raise: Ruby::Nginx::StopError)
15
+ end
16
+
17
+ def run
18
+ super
19
+ rescue Ruby::Nginx::StopError => e
20
+ if @sudo
21
+ # Nginx is not running - ignore.
22
+ raise unless e.message.include?("invalid PID number")
23
+ else
24
+ # Elevate to sudo and try again.
25
+ self.class.new(sudo: true).run
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty/command"
4
+ require "tty/prompt"
5
+
6
+ module Ruby
7
+ module Nginx
8
+ module Commands
9
+ class TerminalCommand
10
+ attr_reader :cmd, :user, :error_type, :printer, :result
11
+
12
+ def initialize(cmd:, user: nil, raise: nil, printer: :null)
13
+ @cmd = cmd
14
+ @user = user
15
+ @error_type = raise
16
+ @printer = ENV["DEBUG"] ? :pretty : printer
17
+ @result = nil
18
+ end
19
+
20
+ def run
21
+ @result = TTY::Command.new(printer: printer).run!(cmd, user: user)
22
+ raise @error_type, @result.err if error_type && @result.failure?
23
+
24
+ @result
25
+ end
26
+
27
+ protected
28
+
29
+ def yes?(question)
30
+ ENV["SKIP_PROMPT"] || TTY::Prompt.new.yes?("[Ruby::Nginx] #{question}")
31
+ end
32
+
33
+ def sudoify(cmd, sudo, reason)
34
+ return cmd unless sudo
35
+
36
+ if sudo_access? || yes?(reason)
37
+ "sudo #{cmd}"
38
+ else
39
+ raise Ruby::Nginx::AbortError, "Operation aborted"
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def sudo_access?
46
+ TerminalCommand.new(cmd: "sudo -n true").run.success?
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "terminal_command"
4
+
5
+ module Ruby
6
+ module Nginx
7
+ module Commands
8
+ class ValidateNginxConfig < TerminalCommand
9
+ def initialize(sudo: false)
10
+ @sudo = sudo
11
+ sudo_reason = "Allow sudo elevation to validate NGINX config?"
12
+ cmd = sudoify("nginx -t", sudo, sudo_reason)
13
+
14
+ super(cmd:, raise: Ruby::Nginx::ConfigError)
15
+ end
16
+
17
+ def run
18
+ super
19
+ rescue Ruby::Nginx::ConfigError
20
+ raise if @sudo
21
+
22
+ # Elevate to sudo and try again.
23
+ self.class.new(sudo: true).run
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "erb"
4
- require_relative "utils/mkcert"
5
- require_relative "utils/safe_file"
4
+ require_relative "constants"
5
+ require_relative "system/mkcert"
6
+ require_relative "system/nginx"
7
+ require_relative "system/safe_file"
6
8
 
7
9
  module Ruby
8
10
  module Nginx
9
11
  class Configuration
10
- DEFAULT_PATH = "~/.ruby-nginx"
12
+ include Ruby::Nginx::Constants
11
13
 
12
14
  attr_accessor :options
13
15
 
@@ -15,6 +17,14 @@ module Ruby
15
17
  @options = defaults.merge(options)
16
18
  end
17
19
 
20
+ def domain
21
+ options[:domain]
22
+ end
23
+
24
+ def host
25
+ options[:host]
26
+ end
27
+
18
28
  def name
19
29
  "ruby_nginx_#{options[:domain].gsub(/\W/, "_")}"
20
30
  end
@@ -48,14 +58,18 @@ module Ruby
48
58
  }
49
59
  end
50
60
 
61
+ def nginx_version
62
+ @nginx_version ||= System::Nginx.version
63
+ end
64
+
51
65
  private
52
66
 
53
67
  def default_path(path)
54
- "~/.ruby-nginx/#{path}"
68
+ "#{CONFIG_PATH}/#{path}"
55
69
  end
56
70
 
57
71
  def realize_option_path!(option)
58
- options[option] = Utils::SafeFile.touch(options[option])
72
+ options[option] = System::SafeFile.touch(options[option])
59
73
  end
60
74
 
61
75
  def validate!
@@ -69,8 +83,7 @@ module Ruby
69
83
  end
70
84
 
71
85
  def create_ssl_certs!
72
- Utils::Mkcert.setup!
73
- Utils::Mkcert.create!(
86
+ System::Mkcert.create!(
74
87
  options[:domain],
75
88
  realize_option_path!(:ssl_certificate_path),
76
89
  realize_option_path!(:ssl_certificate_key_path)
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Nginx
5
+ module Constants
6
+ CONFIG_PATH = "~/.ruby-nginx"
7
+ SERVERS_PATH = "#{CONFIG_PATH}/servers"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Nginx
5
+ module Exceptions
6
+ Error = Class.new(StandardError)
7
+ AbortError = Class.new(Error)
8
+ InstallError = Class.new(Error)
9
+ SetupError = Class.new(Error)
10
+ CreateError = Class.new(Error)
11
+ ConfigError = Class.new(Error)
12
+ StartError = Class.new(Error)
13
+ StopError = Class.new(Error)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../commands/add_host_mapping"
4
+ require_relative "../commands/remove_host_mapping"
5
+
6
+ module Ruby
7
+ module Nginx
8
+ module System
9
+ class Hosts
10
+ class << self
11
+ def add(host, ip)
12
+ remove(host)
13
+ Commands::AddHostMapping.new(host, ip).run.success?
14
+ end
15
+
16
+ def remove(host)
17
+ Commands::RemoveHostMapping.new(host).run.success?
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../commands/install_mkcert"
4
+ require_relative "../commands/setup_mkcert"
5
+ require_relative "../commands/create_mkcert_certificate"
6
+
7
+ module Ruby
8
+ module Nginx
9
+ module System
10
+ class Mkcert
11
+ class << self
12
+ def install!
13
+ Commands::InstallMkcert.new.run
14
+ end
15
+
16
+ def setup!
17
+ Commands::SetupMkcert.new.run
18
+ end
19
+
20
+ def create!(domain, cert_file_path, key_file_path)
21
+ Commands::CreateMkcertCertificate.new(domain, cert_file_path, key_file_path).run
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../commands/add_nginx_config"
4
+ require_relative "../commands/install_nginx"
5
+ require_relative "../commands/nginx_options"
6
+ require_relative "../commands/nginx_version"
7
+ require_relative "../commands/remove_nginx_config"
8
+ require_relative "../commands/setup_nginx"
9
+ require_relative "../commands/start_nginx"
10
+ require_relative "../commands/stop_nginx"
11
+ require_relative "../commands/validate_nginx_config"
12
+
13
+ module Ruby
14
+ module Nginx
15
+ module System
16
+ class Nginx
17
+ class << self
18
+ def version
19
+ Commands::NginxVersion.new.run
20
+ end
21
+
22
+ def options
23
+ Commands::NginxOptions.new.run
24
+ end
25
+
26
+ def install!
27
+ Commands::InstallNginx.new.run
28
+ end
29
+
30
+ def setup!
31
+ Commands::SetupNginx.new.run
32
+ end
33
+
34
+ def add_server_config(name, config)
35
+ Commands::AddNginxConfig.new.run(name, config)
36
+ end
37
+
38
+ def remove_server_config(name)
39
+ Commands::RemoveNginxConfig.new.run(name)
40
+ end
41
+
42
+ def validate_config!
43
+ Commands::ValidateNginxConfig.new.run
44
+ end
45
+
46
+ def start!
47
+ Commands::StartNginx.new.run
48
+ end
49
+
50
+ def stop!
51
+ Commands::StopNginx.new.run
52
+ end
53
+
54
+ def restart!
55
+ stop!
56
+ start!
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "tty/command"
5
+
6
+ module Ruby
7
+ module Nginx
8
+ module System
9
+ class OS
10
+ include Singleton
11
+
12
+ def initialize
13
+ @semaphore = Mutex.new
14
+ end
15
+
16
+ def darwin?
17
+ RbConfig::CONFIG["host_os"] =~ /darwin/
18
+ end
19
+
20
+ def package_manager
21
+ @package_manager ||=
22
+ if darwin?
23
+ :brew if package_with?("brew")
24
+ elsif package_with?("apt-get")
25
+ :apt_get
26
+ elsif package_with?("pacman")
27
+ :pacman
28
+ elsif package_with?("yum")
29
+ :yum
30
+ elsif package_with?("zypper")
31
+ :zypper
32
+ end
33
+
34
+ return @package_manager if @package_manager
35
+ raise Ruby::Nginx::Error, "Could not determine package manager"
36
+ end
37
+
38
+ private
39
+
40
+ def package_with?(package_manager)
41
+ @semaphore.synchronize do
42
+ TTY::Command.new(printer: :null).run!("which #{package_manager}").success?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -4,7 +4,7 @@ require "fileutils"
4
4
 
5
5
  module Ruby
6
6
  module Nginx
7
- module Utils
7
+ module System
8
8
  class SafeFile
9
9
  class << self
10
10
  def touch(file_path)
@@ -16,6 +16,12 @@ module Ruby
16
16
  safe_path
17
17
  end
18
18
 
19
+ def read(file_path)
20
+ safe_path = File.expand_path(file_path)
21
+
22
+ File.read(safe_path)
23
+ end
24
+
19
25
  def write(file_path, content)
20
26
  safe_path = touch(file_path)
21
27
 
@@ -24,10 +30,10 @@ module Ruby
24
30
  safe_path
25
31
  end
26
32
 
27
- private
33
+ def rm(file_path)
34
+ safe_path = File.expand_path(file_path)
28
35
 
29
- def safe_path(file_path)
30
- File.expand_path(file_path)
36
+ FileUtils.rm_f(safe_path)
31
37
  end
32
38
  end
33
39
  end
@@ -38,8 +38,12 @@ server {
38
38
 
39
39
  <% if options[:ssl] %>
40
40
  # ssl
41
+ <% if nginx_version >= Ruby::Nginx::Version.new("1.25.1") %>
41
42
  listen 443 ssl;
42
43
  http2 on;
44
+ <% else %>
45
+ listen 443 ssl http2;
46
+ <% end %>
43
47
  ssl_certificate <%= options[:ssl_certificate_path] %>;
44
48
  ssl_certificate_key <%= options[:ssl_certificate_key_path] %>;
45
49
  <% end %>
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Ruby
4
4
  module Nginx
5
- VERSION = "1.0.0-alpha"
5
+ VERSION = "1.0.0-beta.1"
6
+
7
+ Version = Gem::Version
6
8
  end
7
9
  end
data/lib/ruby/nginx.rb CHANGED
@@ -1,37 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "nginx/configuration"
4
- require_relative "nginx/utils/nginx"
4
+ require_relative "nginx/exceptions"
5
+ require_relative "nginx/system/hosts"
6
+ require_relative "nginx/system/nginx"
5
7
  require_relative "nginx/version"
6
8
 
7
9
  module Ruby
8
10
  module Nginx
9
- class Error < StandardError; end
11
+ include Ruby::Nginx::Exceptions
10
12
 
11
13
  def self.add!(options = {}, &block)
14
+ setup!
12
15
  conf = config(options, &block)
13
16
 
14
- Utils::Nginx.add_server_config(conf.name, conf.generate!)
15
- Utils::Nginx.validate_config!
16
- Utils::Nginx.restart!
17
+ System::Nginx.add_server_config(conf.name, conf.generate!)
18
+ System::Nginx.validate_config!
19
+ System::Nginx.restart!
20
+ System::Hosts.add(conf.domain, conf.host)
17
21
 
18
22
  conf
19
- rescue Utils::Nginx::Error
20
- remove!
23
+ rescue Ruby::Nginx::AbortError
24
+ raise
25
+ rescue Ruby::Nginx::Error
26
+ remove!(options)
21
27
  raise
22
28
  end
23
29
 
24
30
  def self.remove!(options = {}, &block)
25
31
  conf = config(options, &block)
26
32
 
27
- Utils::Nginx.remove_server_config(conf.name)
28
- Utils::Nginx.restart!
33
+ System::Nginx.remove_server_config(conf.name)
34
+ System::Nginx.restart!
35
+ System::Hosts.remove(conf.domain)
29
36
 
30
37
  conf
31
38
  end
32
39
 
33
40
  private
34
41
 
42
+ def self.setup!
43
+ System::Nginx.install!
44
+ System::Nginx.setup!
45
+ System::Mkcert.install!
46
+ System::Mkcert.setup!
47
+ end
48
+ private_class_method :setup!
49
+
35
50
  def self.config(options = {})
36
51
  conf = Configuration.new(options)
37
52
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-nginx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.alpha
4
+ version: 1.0.0.pre.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bert McCutchen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-22 00:00:00.000000000 Z
11
+ date: 2025-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: erb
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: open3
28
+ name: thor
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,19 +39,45 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: thor
42
+ name: tty-command
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.10'
45
48
  - - ">="
46
49
  - !ruby/object:Gem::Version
47
- version: '0'
50
+ version: 0.10.1
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '0.10'
52
58
  - - ">="
53
59
  - !ruby/object:Gem::Version
54
- version: '0'
60
+ version: 0.10.1
61
+ - !ruby/object:Gem::Dependency
62
+ name: tty-prompt
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.23'
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 0.23.1
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '0.23'
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 0.23.1
55
81
  description: Utility gem with an added CLI for configuring NGINX with SSL.
56
82
  email:
57
83
  - mail@bertm.dev
@@ -66,12 +92,30 @@ files:
66
92
  - exe/ruby-nginx
67
93
  - lib/ruby/nginx.rb
68
94
  - lib/ruby/nginx/cli.rb
95
+ - lib/ruby/nginx/commands/add_host_mapping.rb
96
+ - lib/ruby/nginx/commands/add_nginx_config.rb
97
+ - lib/ruby/nginx/commands/create_mkcert_certificate.rb
98
+ - lib/ruby/nginx/commands/install_mkcert.rb
99
+ - lib/ruby/nginx/commands/install_nginx.rb
100
+ - lib/ruby/nginx/commands/nginx_options.rb
101
+ - lib/ruby/nginx/commands/nginx_version.rb
102
+ - lib/ruby/nginx/commands/remove_host_mapping.rb
103
+ - lib/ruby/nginx/commands/remove_nginx_config.rb
104
+ - lib/ruby/nginx/commands/setup_mkcert.rb
105
+ - lib/ruby/nginx/commands/setup_nginx.rb
106
+ - lib/ruby/nginx/commands/start_nginx.rb
107
+ - lib/ruby/nginx/commands/stop_nginx.rb
108
+ - lib/ruby/nginx/commands/terminal_command.rb
109
+ - lib/ruby/nginx/commands/validate_nginx_config.rb
69
110
  - lib/ruby/nginx/configuration.rb
111
+ - lib/ruby/nginx/constants.rb
112
+ - lib/ruby/nginx/exceptions.rb
113
+ - lib/ruby/nginx/system/hosts.rb
114
+ - lib/ruby/nginx/system/mkcert.rb
115
+ - lib/ruby/nginx/system/nginx.rb
116
+ - lib/ruby/nginx/system/os.rb
117
+ - lib/ruby/nginx/system/safe_file.rb
70
118
  - lib/ruby/nginx/templates/nginx.conf.erb
71
- - lib/ruby/nginx/utils/command.rb
72
- - lib/ruby/nginx/utils/mkcert.rb
73
- - lib/ruby/nginx/utils/nginx.rb
74
- - lib/ruby/nginx/utils/safe_file.rb
75
119
  - lib/ruby/nginx/version.rb
76
120
  homepage: https://github.com/bert-mccutchen/ruby-nginx
77
121
  licenses:
@@ -81,7 +125,7 @@ metadata:
81
125
  source_code_uri: https://github.com/bert-mccutchen/ruby-nginx
82
126
  changelog_uri: https://github.com/bert-mccutchen/ruby-nginx/blob/main/CHANGELOG.md
83
127
  rubygems_mfa_required: 'true'
84
- post_install_message:
128
+ post_install_message:
85
129
  rdoc_options: []
86
130
  require_paths:
87
131
  - lib
@@ -97,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
141
  version: '0'
98
142
  requirements: []
99
143
  rubygems_version: 3.5.22
100
- signing_key:
144
+ signing_key:
101
145
  specification_version: 4
102
146
  summary: Utility for configuring NGINX with SSL.
103
147
  test_files: []
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ruby
4
- module Nginx
5
- module Utils
6
- class Command
7
- attr_reader :stdout, :stderr, :status
8
-
9
- def initialize(raise: nil)
10
- @error_type = raise
11
- @stdout = nil
12
- @stderr = nil
13
- @status = nil
14
- end
15
-
16
- def run(cmd)
17
- @stdout, @stderr, @status = Open3.capture3(cmd)
18
- raise @error_type, @stderr if @error_type && !@status.success?
19
-
20
- self
21
- end
22
-
23
- def self.run(cmd)
24
- new.run(cmd)
25
- end
26
- end
27
- end
28
- end
29
- end