git_toolbox 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a06dde4facc3e0dff5fe756ae0d5bbc3b373a71ee79f2b677f8a088ee72bf60
4
- data.tar.gz: b9f3883565a800112d61a67bc15ec348cce8e1914037ef68e1fe3b15704c0024
3
+ metadata.gz: 500f22d2000495865a5671476122c580ea0e5fdb6ebf2804ce42d209828a1841
4
+ data.tar.gz: 70a3c312974ce5ee382a8c6793325bf676dba7fb40206ed828f56c2019ce9d64
5
5
  SHA512:
6
- metadata.gz: 960f230cd4e90e82ad1ac61313d237071491eb7f1f7461ae7246d7e4af7c2f56d6c2b9731be61233b70df35bfd1fb87b4c5d12c710de21f967348ef351be5787
7
- data.tar.gz: 996321047eff57ea6a066059659208c3ab6433686f125fbf5729b5c9f9574ff3320b38777b08daec98b5151a4863b367e7817eb3f4190c9d0f806ebb3128a07c
6
+ metadata.gz: ce3a643b6a110c7b70e7ddc33649548b15f064e61dbe35c13fa5eba3fa70b7c71a5118185ee88b89aab83f74602b605d9175b4633e58c933ae30ebfae97c2dab
7
+ data.tar.gz: 5125a3a0af8ab4a6590af9166c95d442c1dae5f89648f1d94224ff5a4d3e18fcaf21ec5e9d52b1f50939340d17d60ddd5cd37fa7bd6bd16b4e7ce88f2ac868da
data/bin/get CHANGED
@@ -5,4 +5,4 @@ require 'rubygems'
5
5
  require 'bundler/setup'
6
6
  require 'get'
7
7
 
8
- Get.main
8
+ Get.instance.main
@@ -0,0 +1,60 @@
1
+ # Get is a toolbox based on git which simplifies the adoption of conventions and some git commands.
2
+ # Copyright (C) 2023 Alex Speranza
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Lesser General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program and the additional permissions granted by
16
+ # the Lesser GPL. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require 'open3'
21
+
22
+ # Module for simplify command execution.
23
+ module CommandIssuer
24
+ # A class containing the result of a command, including the exit status and the command executed.
25
+ class CommandResult
26
+ attr_reader :command, :exit_status, :output, :error
27
+
28
+ def initialize(command_string, exit_status, standard_output, standard_error)
29
+ @command = command_string
30
+ @exit_status = exit_status
31
+ @output = standard_output
32
+ @error = standard_error
33
+ end
34
+ end
35
+
36
+ def self.run(executable, *args)
37
+ full_path_executable = CommandIssuer.send(:find, executable)
38
+ command = [full_path_executable, *args].join(' ').strip
39
+ output, error, status = Open3.capture3(command)
40
+ CommandResult.new(command, status.exitstatus, output, error)
41
+ end
42
+
43
+ class << self
44
+ private
45
+
46
+ # Checks if the given executable exists. If it does not exists,
47
+ # an error message will be displayed and the program will exit.
48
+ # Based on https://stackoverflow.com/a/5471032
49
+ def find(executable)
50
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
51
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
52
+ exts.each do |ext|
53
+ exe = File.join(path, "#{executable}#{ext}")
54
+ return exe if File.executable?(exe) && !File.directory?(exe)
55
+ end
56
+ end
57
+ Common.error("'#{executable}' was not found in PATH.")
58
+ end
59
+ end
60
+ end
@@ -51,4 +51,74 @@ module Common
51
51
  action.call if action.respond_to?('call')
52
52
  exit(exit_code)
53
53
  end
54
+
55
+ # Add an instance attribute (with a default value) to a module.
56
+ # It is intended to be called in the body of a module definition:
57
+ # module MyModule
58
+ # DEFAULT_VALUE = 1
59
+ # Common.module_instance_attr(self, my_variable, DEFAULT_VALUE)
60
+ # end
61
+ # produces the code:
62
+ # module MyModule
63
+ # instance_variable_set(:@my_variable, 1)
64
+ # def self.my_variable
65
+ # instance_variable_get(:@my_variable)
66
+ # end
67
+ #
68
+ # def self.my_variable=(value)
69
+ # instance_variable_set(:@my_variable, value)
70
+ # end
71
+ # end
72
+ def self.module_instance_attr(mod, name, default_value = nil)
73
+ mod.module_eval(<<~CODE, __FILE__, __LINE__ + 1)
74
+ # module MyModule
75
+ # instance_variable_set(:@my_variable, 1)
76
+ # def self.my_variable
77
+ # instance_variable_get(:@my_variable)
78
+ # end
79
+ #
80
+ # def self.my_variable=(value)
81
+ # instance_variable_set(:@my_variable, value)
82
+ # end
83
+ # end
84
+
85
+ instance_variable_set(:@#{name}, #{default_value})
86
+
87
+ def self.#{name}
88
+ instance_variable_get(:@#{name})
89
+ end
90
+
91
+ def self.#{name}=(value)
92
+ instance_variable_set(:@#{name}, value)
93
+ end
94
+ CODE
95
+ end
96
+
97
+ def self.module_instance_value(mod, name, value)
98
+ mod.module_eval(<<~CODE, __FILE__, __LINE__ + 1)
99
+ # module MyModule
100
+ # instance_variable_set(:@my_variable, 1)
101
+ # def self.my_variable
102
+ # instance_variable_get(:@my_variable)
103
+ # end
104
+ # end
105
+
106
+ instance_variable_set(:@#{name}, #{value})
107
+
108
+ def self.#{name}
109
+ instance_variable_get(:@#{name})
110
+ end
111
+ CODE
112
+ end
113
+
114
+ # Add a new 'MOD_REF' constant with the module symbol for a shorter variable name.
115
+ def self.add_module_self_reference(mod)
116
+ mod.module_eval(<<~CODE, __FILE__, __LINE__ + 1)
117
+ # module MyModule
118
+ # MOD_REF = MyModule
119
+ # end
120
+
121
+ MOD_REF = #{mod.name}
122
+ CODE
123
+ end
54
124
  end
@@ -17,23 +17,17 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- require 'English'
21
- require 'get/commons/common'
20
+ require_relative './command_issuer'
22
21
 
23
22
  # Utility module
24
23
  module Git
25
24
  # Groups: 1 = type, 2 = scope with (), 3 = scope, 4 = breaking change, 5 = summary
26
- CONVENTIONAL_COMMIT_REGEX = /^(\w+)(\(([\w-]+)\))?(!)?:(.*)/
25
+ CONVENTIONAL_COMMIT_REGEX = %r{^(\w+)(\(([\w/-]+)\))?(!)?:(.*)}
27
26
 
28
27
  # Check if the command is called while in a git repository.
29
28
  # If the command fails, it is assumed to not be in a git repository.
30
29
  def self.in_repo?
31
- system('git rev-parse --is-inside-work-tree &>/dev/null')
32
- case $CHILD_STATUS.exitstatus
33
- when 0 then true
34
- when 127 then Common.error '"git" is not installed.'
35
- else false
36
- end
30
+ CommandIssuer.run('git', 'rev-parse', '--is-inside-work-tree').exit_status.zero?
37
31
  end
38
32
 
39
33
  # Run a block of code with the list of commits from the given version as an argument.
@@ -41,26 +35,42 @@ module Git
41
35
  def self.with_commit_list_from(version = nil, &block)
42
36
  return unless block_given?
43
37
 
44
- commits_from_version =
45
- `git --no-pager log --oneline --pretty=format:%s #{version.nil? ? '' : "^#{version} HEAD"} 2>/dev/null`
46
- .split("\n")
47
- .then { |value| $CHILD_STATUS.exitstatus.zero? ? value : [] }
38
+ command_result = CommandIssuer.run(
39
+ 'git',
40
+ '--no-pager',
41
+ 'log',
42
+ '--oneline',
43
+ '--pretty=format:%s',
44
+ version.nil? ? '' : "^#{version} HEAD"
45
+ )
46
+ commits_from_version = if command_result.exit_status.zero?
47
+ command_result.output.split("\n")
48
+ else
49
+ []
50
+ end
48
51
  block.call(commits_from_version)
49
52
  end
50
53
 
51
54
  # Returns the last version and caches it for the next calls.
52
55
  def self.last_version
53
- @@last_version ||=
54
- `git describe --tags --abbrev=0 2>/dev/null`
55
- .strip
56
- .then { |value| value if $CHILD_STATUS.exitstatus.zero? }
56
+ @last_version ||=
57
+ CommandIssuer.run('git', 'describe', '--tags', '--abbrev=0')
58
+ .then { |result| result.output.strip if result.exit_status.zero? }
57
59
  end
58
60
 
59
61
  # Returns the last release and caches it for the next calls.
60
62
  def self.last_release
61
- @@last_release ||=
62
- `git --no-pager tag --list | sed 's/+/_/' | sort -V | sed 's/_/+/' | tail -n 1`
63
- .strip
64
- .then { |value| value unless value.empty? }
63
+ @last_release ||=
64
+ CommandIssuer.run('git', '--no-pager', 'tag', '--list')
65
+ .then do |value|
66
+ unless value.output.empty?
67
+ value.output
68
+ .split("\n")
69
+ .map { |str| str.sub('+', '_') }
70
+ .sort
71
+ .map { |str| str.sub('_', '+') }
72
+ .last
73
+ end
74
+ end
65
75
  end
66
76
  end
@@ -0,0 +1,79 @@
1
+ # Get is a toolbox based on git which simplifies the adoption of conventions and some git commands.
2
+ # Copyright (C) 2023 Alex Speranza
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Lesser General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program and the additional permissions granted by
16
+ # the Lesser GPL. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require 'net/http'
21
+ require 'uri'
22
+
23
+ # Client HTTP which allows to perform GET request and follow redirections.
24
+ class HTTPClient
25
+ include Singleton
26
+
27
+ # Number of attempts to try when following a redirection link.
28
+ MAX_ATTEMPTS = 10
29
+
30
+ # Perform a get request to an address, following the redirections at most MAX_ATTEMPTS times.
31
+ def http_get_request(address)
32
+ uri = URI.parse(address)
33
+
34
+ # Code based on https://shadow-file.blogspot.com/2009/03/handling-http-redirection-in-ruby.html
35
+ attempts = 0
36
+ until attempts >= MAX_ATTEMPTS
37
+ attempts += 1
38
+
39
+ resp = build_http(uri)
40
+ .request(Net::HTTP::Get.new(uri.path == '' ? '/' : uri.path))
41
+ return resp if resp.is_a?(Net::HTTPSuccess) || resp.header['location'].nil?
42
+
43
+ uri = updated_uri(uri, resp.header['location'])
44
+ end
45
+ nil
46
+ end
47
+
48
+ def response_error_message(response)
49
+ if response.nil?
50
+ 'too many redirections'
51
+ else
52
+ "#{response.code} #{response.message}"
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def build_http(uri)
59
+ Net::HTTP.new(uri.host, uri.port)
60
+ .tap { |http| http.open_timeout = 10 }
61
+ .tap { |http| http.read_timeout = 10 }
62
+ .tap do |http|
63
+ if uri.instance_of? URI::HTTPS
64
+ http.use_ssl = true
65
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
66
+ end
67
+ end
68
+ end
69
+
70
+ def updated_uri(old_uri, location)
71
+ URI.parse(location).then do |value|
72
+ if value.relative?
73
+ old_uri + location
74
+ else
75
+ value
76
+ end
77
+ end
78
+ end
79
+ end
@@ -17,22 +17,14 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- require 'English'
21
- require 'get/commons/common'
22
- require 'get/commons/git'
23
- require 'get/subcommand/command'
20
+ require_relative '../../commons/common'
21
+ require_relative '../../commons/git'
22
+ require_relative '../command'
24
23
 
25
24
  # Class length is disabled as most of its length is given by formatting.
26
25
  # rubocop:disable Metrics/ClassLength
27
26
  # Subcommand, generates a changelog.
28
27
  class Changelog < Command
29
- def self.command
30
- @@command ||= new
31
- @@command
32
- end
33
-
34
- private_class_method :new
35
-
36
28
  private
37
29
 
38
30
  UNDEFINED_SCOPE = 'other'
@@ -44,56 +36,11 @@ class Changelog < Command
44
36
  item: '- %s'
45
37
  }.freeze
46
38
 
47
- @@command = nil
48
-
49
- @@usage = 'changelog -h|(<subcommand> [<subcommand-options])'
50
- @@description = 'Generate a changelog. Format options require a "%s" where the content must be.'
51
- @@subcommands = {}
52
- # This block is Optimist configuration. It is as long as the number of options of the command.
53
- # rubocop:disable Metrics/BlockLength
54
- @@option_parser = Optimist::Parser.new do
55
- subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
56
- subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
57
- Subcommands:
58
- #{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
59
- SUBCOMMANDS
60
- usage @@usage
61
- synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
62
- opt :latest,
63
- 'Generate the changelog from the latest version rather than the latest release'
64
- opt :title_format,
65
- 'Set the symbol for the title.',
66
- { type: :string, short: 'T', default: '# %s' }
67
- opt :type_format,
68
- 'Set the symbol for the commit types.',
69
- { type: :string, short: 't', default: '= %s' }
70
- opt :scope_format,
71
- 'Set the symbol for the commit scopes.',
72
- { type: :string, short: 's', default: '- %s' }
73
- opt :list_format,
74
- 'Set the symbol for lists.',
75
- { type: :string, short: 'l', default: '%s' }
76
- opt :item_format,
77
- 'Set the symbol for list items.',
78
- { type: :string, short: 'i', default: '* %s' }
79
- opt :markdown,
80
- 'Shortcut for `-T "# %s" -t "## %s" -s "### %s" -l "%s" -i "- %s"`. ' \
81
- 'Can be overwritten by the single options.'
82
- educate_on_error
83
- stop_on @@subcommands.keys.map(&:to_s)
84
- end
85
- # rubocop:enable Metrics/BlockLength
86
-
87
39
  def initialize
88
- super(@@usage, @@description) do
89
- @options = Common.with_subcommand_exception_handling @@option_parser do
90
- @@option_parser.parse
91
- end
92
- Common.error 'changelog need to be run inside a git repository' unless Git.in_repo?
93
- @format = {}
94
- set_format
95
-
96
- puts changelog_from(@options[:latest] ? Git.last_version : Git.last_release)
40
+ super() do
41
+ @usage = 'changelog -h|(<subcommand> [<subcommand-options])'
42
+ @description = 'Generate a changelog. Format options require a "%s" where the content must be.'
43
+ @subcommands = {}
97
44
  end
98
45
  end
99
46
 
@@ -161,5 +108,53 @@ class Changelog < Command
161
108
  #{@format[:list].sub('%s', formatted_commits.join("\n"))}
162
109
  SCOPE
163
110
  end
111
+
112
+ protected
113
+
114
+ def setup_option_parser
115
+ @option_parser = Optimist::Parser.new(
116
+ @usage,
117
+ full_description,
118
+ stop_condition
119
+ ) do |usage_header, description, stop_condition|
120
+ usage usage_header
121
+ synopsis description
122
+ opt :latest,
123
+ 'Generate the changelog from the latest version rather than the latest release'
124
+ opt :title_format,
125
+ 'Set the symbol for the title.',
126
+ { type: :string, short: 'T', default: '# %s' }
127
+ opt :type_format,
128
+ 'Set the symbol for the commit types.',
129
+ { type: :string, short: 't', default: '= %s' }
130
+ opt :scope_format,
131
+ 'Set the symbol for the commit scopes.',
132
+ { type: :string, short: 's', default: '- %s' }
133
+ opt :list_format,
134
+ 'Set the symbol for lists.',
135
+ { type: :string, short: 'l', default: '%s' }
136
+ opt :item_format,
137
+ 'Set the symbol for list items.',
138
+ { type: :string, short: 'i', default: '* %s' }
139
+ opt :markdown,
140
+ 'Shortcut for `-T "# %s" -t "## %s" -s "### %s" -l "%s" -i "- %s"`. ' \
141
+ 'Can be overwritten by the single options.'
142
+ educate_on_error
143
+ stop_on stop_condition
144
+ end
145
+ end
146
+
147
+ def setup_action
148
+ @action = lambda do
149
+ @options = Common.with_subcommand_exception_handling @option_parser do
150
+ @option_parser.parse
151
+ end
152
+ Common.error 'changelog need to be run inside a git repository' unless Git.in_repo?
153
+ @format = {}
154
+ set_format
155
+
156
+ puts changelog_from(@options[:latest] ? Git.last_version : Git.last_release)
157
+ end
158
+ end
164
159
  end
165
160
  # rubocop:enable Metrics/ClassLength
@@ -17,15 +17,67 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- # Base class for (sub)commands. (Sub)Commands should be singletons.
20
+ require 'singleton'
21
+
22
+ # Base class for (sub)commands.
21
23
  class Command
22
- attr_reader :description, :action
24
+ include Singleton
25
+
26
+ attr_reader :usage, :description, :action, :subcommands, :option_parser
23
27
 
24
28
  protected
25
29
 
26
- def initialize(usage, description, &action)
27
- @usage = usage
28
- @description = description
29
- @action = action
30
+ attr_writer :usage, :description, :action, :subcommands, :option_parser
31
+
32
+ def initialize
33
+ super
34
+ yield self if block_given?
35
+ setup_option_parser
36
+ if @option_parser.nil?
37
+ raise("No variable '@option_parser' has been created in the option_parser setup of command #{self.class.name}.")
38
+ end
39
+
40
+ setup_action
41
+ return unless @action.nil?
42
+
43
+ raise("No variable '@action' has been created in the action setup of the command #{self.class.name}")
44
+ end
45
+
46
+ @description = ''
47
+ @subcommands = {}
48
+
49
+ # This method must be overridden by subclasses to create a new option parser in a '@option_parser' variable.
50
+ # Do not call 'super' in the new implementation.
51
+ def setup_option_parser
52
+ raise("Error: command #{self.class.name} do not have a defined option parser.")
53
+ end
54
+
55
+ # This method must be overridden by subclasses to create a new option parser in a '@action' variable.
56
+ # Do not call 'super' in the new implementation.
57
+ def setup_action
58
+ raise("Error: command #{self.class.name} do not have a defined action.")
59
+ end
60
+
61
+ def full_description
62
+ description + if subcommands.empty?
63
+ ''
64
+ else
65
+ subcommand_max_length = subcommands.keys.map { |k| k.to_s.length }.max || 0
66
+ <<~SUBCOMMANDS.chomp
67
+ \n
68
+ Subcommands:
69
+ #{subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{subcommands[k].description}" }.join("\n")}
70
+ SUBCOMMANDS
71
+ end
72
+ end
73
+
74
+ def stop_condition
75
+ subcommands.keys.map(&:to_s)
76
+ end
77
+
78
+ def educated_error(message)
79
+ Common.error message do
80
+ @option_parser.educate
81
+ end
30
82
  end
31
83
  end
@@ -17,77 +17,24 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- require 'English'
21
- require 'get/commons/common'
22
- require 'get/commons/git'
23
- require 'get/subcommand/command'
24
- require 'get/subcommand/commit/prompt'
20
+ require_relative '../../commons/common'
21
+ require_relative '../../commons/git'
22
+ require_relative '../command'
23
+ require_relative './prompt'
25
24
 
26
25
  # Class length is disabled as most of its length is given by formatting.
27
26
  # rubocop:disable Metrics/ClassLength
28
27
  # Subcommand, it manages the description of the current git repository using semantic version.
29
28
  class Commit < Command
30
- def self.command
31
- @@command ||= new
32
- @@command
33
- end
34
-
35
- private_class_method :new
36
-
37
29
  private
38
30
 
39
31
  include PromptHandler
40
32
 
41
- @@command = nil
42
-
43
- @@usage = 'commit -h|(<subcommand> [<subcommand-options])'
44
- @@description = 'Create a new semantic commit.'
45
- @@subcommands = {}
46
- # This block is Optimist configuration. It is as long as the number of options of the command.
47
- # rubocop:disable Metrics/BlockLength
48
- @@option_parser = Optimist::Parser.new do
49
- subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
50
- subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
51
- Subcommands:
52
- #{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
53
- SUBCOMMANDS
54
- usage @@usage
55
- synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
56
- opt :type,
57
- 'Define the type of the commit. Enabling this option skips the type selection.',
58
- { type: :string }
59
- opt :scope,
60
- 'Define the scope of the commit. Enabling this option skips the scope selection.',
61
- { type: :string, short: 'S' }
62
- opt :summary,
63
- 'Define the summary message of the commit. Enabling this option skips the summary message prompt.',
64
- { type: :string, short: 's' }
65
- opt :message,
66
- 'Define the message body of the commit. Enabling this option skips the message body prompt.',
67
- { type: :string }
68
- opt :breaking,
69
- 'Set the commit to have a breaking change. ' \
70
- 'Can be negated with "--no-breaking". ' \
71
- 'Enabling this option skips the breaking change prompt.',
72
- { type: :flag, short: :none }
73
- educate_on_error
74
- stop_on @@subcommands.keys.map(&:to_s)
75
- end
76
- # rubocop:enable Metrics/BlockLength
77
-
78
33
  def initialize
79
- super(@@usage, @@description) do
80
- @options = Common.with_subcommand_exception_handling @@option_parser do
81
- @@option_parser.parse
82
- end
83
- Common.error 'commit need to be run inside a git repository' unless Git.in_repo?
84
-
85
- message = full_commit_message
86
- puts message
87
- output = `git commit --no-status -m "#{message.gsub('"', '\"')}"`
88
- Common.error "git commit failed: #{output}" if $CHILD_STATUS.exitstatus.positive?
89
- rescue Interrupt
90
- Common.print_then_do_and_exit "\nCommit cancelled"
34
+ super() do
35
+ @usage = 'commit -h|(<subcommand> [<subcommand-options])'
36
+ @description = 'Create a new semantic commit.'
37
+ @subcommands = {}
91
38
  end
92
39
  end
93
40
 
@@ -143,5 +90,55 @@ class Commit < Command
143
90
  ask_for_message
144
91
  end.to_s.strip
145
92
  end
93
+
94
+ protected
95
+
96
+ def setup_option_parser
97
+ @option_parser = Optimist::Parser.new(
98
+ @usage,
99
+ full_description,
100
+ stop_condition
101
+ ) do |usage_header, description, stop_condition|
102
+ usage usage_header
103
+ synopsis description
104
+ opt :type,
105
+ 'Define the type of the commit. Enabling this option skips the type selection.',
106
+ { type: :string }
107
+ opt :scope,
108
+ 'Define the scope of the commit. Enabling this option skips the scope selection.',
109
+ { type: :string, short: 'S' }
110
+ opt :summary,
111
+ 'Define the summary message of the commit. Enabling this option skips the summary message prompt.',
112
+ { type: :string, short: 's' }
113
+ opt :message,
114
+ 'Define the message body of the commit. Enabling this option skips the message body prompt.',
115
+ { type: :string }
116
+ opt :breaking,
117
+ 'Set the commit to have a breaking change. ' \
118
+ 'Can be negated with "--no-breaking". ' \
119
+ 'Enabling this option skips the breaking change prompt.',
120
+ { type: :flag, short: :none }
121
+ opt :quiet,
122
+ 'Disable the print of the complete message.'
123
+ educate_on_error
124
+ stop_on stop_condition
125
+ end
126
+ end
127
+
128
+ def setup_action
129
+ @action = lambda do
130
+ @options = Common.with_subcommand_exception_handling @option_parser do
131
+ @option_parser.parse
132
+ end
133
+ Common.error 'commit need to be run inside a git repository' unless Git.in_repo?
134
+
135
+ message = full_commit_message
136
+ puts message unless @options[:quiet]
137
+ command_result = CommandIssuer.run('git', 'commit', '--no-status', '-m', "\"#{message.gsub('"', '\"')}\"")
138
+ Common.error "git commit failed: #{command_result.output}" if command_result.exit_status.positive?
139
+ rescue Interrupt
140
+ Common.print_then_do_and_exit "\nCommit cancelled"
141
+ end
142
+ end
146
143
  end
147
144
  # rubocop:enable Metrics/ClassLength