conventional_commits 0.1.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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +13 -0
  4. data/Gemfile +12 -0
  5. data/Gemfile.lock +116 -0
  6. data/README.md +29 -0
  7. data/Rakefile +12 -0
  8. data/conventional_commits.gemspec +41 -0
  9. data/lib/branch/branch_name_generator.rb +73 -0
  10. data/lib/cli/branch_name_cli.rb +71 -0
  11. data/lib/commit/commit_message_generator.rb +50 -0
  12. data/lib/commit/commit_message_parser.rb +40 -0
  13. data/lib/commit/commit_message_validator.rb +21 -0
  14. data/lib/configuration/branch_configuration_reader.rb +13 -0
  15. data/lib/configuration/main_configuration_reader.rb +26 -0
  16. data/lib/configuration.rb +9 -0
  17. data/lib/conventional_commits/version.rb +5 -0
  18. data/lib/conventional_commits.rb +8 -0
  19. data/lib/git/git.rb +17 -0
  20. data/lib/helpers/file_extension.rb +43 -0
  21. data/lib/hooks/hooks_installer.rb +77 -0
  22. data/lib/models/branch_configuration.rb +16 -0
  23. data/lib/models/commit_configuration.rb +16 -0
  24. data/lib/models/main_configuration.rb +18 -0
  25. data/lib/models/release_configuration.rb +14 -0
  26. data/lib/models/release_rule.rb +15 -0
  27. data/lib/models/type_configuration.rb +31 -0
  28. data/sig/conventional_commit/branch_name_generator.rbs +10 -0
  29. data/sig/conventional_commits/branch_name_cli.rbs +8 -0
  30. data/sig/conventional_commits/branch_name_generator.rbs +7 -0
  31. data/sig/conventional_commits/commit_message.rbs +5 -0
  32. data/sig/conventional_commits/configuration/branch_configuration.rbs +16 -0
  33. data/sig/conventional_commits/configuration/branch_configuration_reader.rbs +7 -0
  34. data/sig/conventional_commits/configuration/main_configuration.rbs +14 -0
  35. data/sig/conventional_commits/configuration/main_configuration_reader.rbs +7 -0
  36. data/sig/conventional_commits/configuration/release_configuration.rbs +9 -0
  37. data/sig/conventional_commits/configuration/release_rule.rbs +8 -0
  38. data/sig/conventional_commits/configuration/type_configuration.rbs +11 -0
  39. data/sig/conventional_commits/git.rbs +7 -0
  40. data/sig/conventional_commits/system_caller.rbs +5 -0
  41. data/sig/conventional_commits.rbs +4 -0
  42. data/sig/file.rbs +7 -0
  43. data/sig/generic_error.rbs +3 -0
  44. data/sig/support/mocks/file_mock_operations.rbs +9 -0
  45. data/sig/support/mocks/mock_data.rbs +12 -0
  46. metadata +173 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 773e8500d2a910d271e21c329b6b2341c93929e86b6a35310abe967bb9a701e8
4
+ data.tar.gz: 34910f2e6067b60c823a2e4f8f1aa6ce6d44472ac47d65ce63de5d22c6119f1a
5
+ SHA512:
6
+ metadata.gz: d5af3444a122c72e7946064d490393ff4ed774acc8f6e6e8d22adac7852bebabc173fd98da25c0790826a63d156048b6ab93dd36fcce2f5dc2ea1227c849438a
7
+ data.tar.gz: 291379e49c6ac188ec384987b92a43dbaae811694304b0ac92e9c4994390b5cf452a477fc8e30f46a5d6eb1210550271f06682a71352360eeb1ff10f3dfc14e2
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in conventional_commits.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "rspec", "~> 3.0"
10
+ gem "rubocop", "~> 1.21"
11
+ gem "simplecov"
12
+
data/Gemfile.lock ADDED
@@ -0,0 +1,116 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ conventional_commits (0.1.0)
5
+ open3
6
+ thor
7
+ yaml
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ aruba (2.2.0)
13
+ bundler (>= 1.17, < 3.0)
14
+ contracts (>= 0.16.0, < 0.18.0)
15
+ cucumber (>= 8.0, < 10.0)
16
+ rspec-expectations (~> 3.4)
17
+ thor (~> 1.0)
18
+ ast (2.4.2)
19
+ bigdecimal (3.1.6)
20
+ builder (3.2.4)
21
+ contracts (0.17)
22
+ cucumber (9.1.2)
23
+ builder (~> 3.2, >= 3.2.4)
24
+ cucumber-ci-environment (~> 9.2, >= 9.2.0)
25
+ cucumber-core (~> 12.0)
26
+ cucumber-cucumber-expressions (~> 17.0)
27
+ cucumber-gherkin (> 24, < 27)
28
+ cucumber-html-formatter (> 20.3, < 22)
29
+ cucumber-messages (> 19, < 25)
30
+ diff-lcs (~> 1.5)
31
+ mini_mime (~> 1.1, >= 1.1.5)
32
+ multi_test (~> 1.1, >= 1.1.0)
33
+ sys-uname (~> 1.2, >= 1.2.3)
34
+ cucumber-ci-environment (9.2.0)
35
+ cucumber-core (12.0.0)
36
+ cucumber-gherkin (>= 25, < 27)
37
+ cucumber-messages (>= 20, < 23)
38
+ cucumber-tag-expressions (~> 5.0, >= 5.0.4)
39
+ cucumber-cucumber-expressions (17.0.2)
40
+ bigdecimal
41
+ cucumber-gherkin (26.2.0)
42
+ cucumber-messages (>= 19.1.4, < 22.1)
43
+ cucumber-html-formatter (21.2.0)
44
+ cucumber-messages (> 19, < 25)
45
+ cucumber-messages (22.0.0)
46
+ cucumber-tag-expressions (5.0.6)
47
+ diff-lcs (1.5.1)
48
+ docile (1.4.0)
49
+ ffi (1.16.3)
50
+ json (2.7.1)
51
+ language_server-protocol (3.17.0.3)
52
+ mini_mime (1.1.5)
53
+ multi_test (1.1.0)
54
+ open3 (0.1.1)
55
+ parallel (1.24.0)
56
+ parser (3.3.0.5)
57
+ ast (~> 2.4.1)
58
+ racc
59
+ racc (1.7.3)
60
+ rainbow (3.1.1)
61
+ rake (13.1.0)
62
+ regexp_parser (2.9.0)
63
+ rexml (3.2.6)
64
+ rspec (3.13.0)
65
+ rspec-core (~> 3.13.0)
66
+ rspec-expectations (~> 3.13.0)
67
+ rspec-mocks (~> 3.13.0)
68
+ rspec-core (3.13.0)
69
+ rspec-support (~> 3.13.0)
70
+ rspec-expectations (3.13.0)
71
+ diff-lcs (>= 1.2.0, < 2.0)
72
+ rspec-support (~> 3.13.0)
73
+ rspec-mocks (3.13.0)
74
+ diff-lcs (>= 1.2.0, < 2.0)
75
+ rspec-support (~> 3.13.0)
76
+ rspec-support (3.13.1)
77
+ rubocop (1.62.1)
78
+ json (~> 2.3)
79
+ language_server-protocol (>= 3.17.0)
80
+ parallel (~> 1.10)
81
+ parser (>= 3.3.0.2)
82
+ rainbow (>= 2.2.2, < 4.0)
83
+ regexp_parser (>= 1.8, < 3.0)
84
+ rexml (>= 3.2.5, < 4.0)
85
+ rubocop-ast (>= 1.31.1, < 2.0)
86
+ ruby-progressbar (~> 1.7)
87
+ unicode-display_width (>= 2.4.0, < 3.0)
88
+ rubocop-ast (1.31.2)
89
+ parser (>= 3.3.0.4)
90
+ ruby-progressbar (1.13.0)
91
+ simplecov (0.22.0)
92
+ docile (~> 1.1)
93
+ simplecov-html (~> 0.11)
94
+ simplecov_json_formatter (~> 0.1)
95
+ simplecov-html (0.12.3)
96
+ simplecov_json_formatter (0.1.4)
97
+ sys-uname (1.2.3)
98
+ ffi (~> 1.1)
99
+ thor (1.3.1)
100
+ unicode-display_width (2.5.0)
101
+ yaml (0.3.0)
102
+
103
+ PLATFORMS
104
+ arm64-darwin-23
105
+
106
+ DEPENDENCIES
107
+ aruba
108
+ conventional_commits!
109
+ cucumber
110
+ rake (~> 13.0)
111
+ rspec (~> 3.0)
112
+ rubocop (~> 1.21)
113
+ simplecov
114
+
115
+ BUNDLED WITH
116
+ 2.3.14
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # ConventionalCommits
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/conventional_commits`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add conventional_commits
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install conventional_commits
16
+
17
+ ## Usage
18
+
19
+ TODO: Write usage instructions here
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/conventional_commits.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/conventional_commits/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "conventional_commits"
7
+ spec.version = ConventionalCommits::VERSION
8
+ spec.authors = "Swift-Gurus"
9
+ spec.email = "alexei.hmelevski@gmail.com"
10
+
11
+ spec.summary = "Make your commit unified."
12
+ spec.description = "Enforces conventional commits specs."
13
+ spec.homepage = "https://github.com/Swift-Gurus/conventional_commits"
14
+ spec.required_ruby_version = ">= 3.1.0"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/Swift-Gurus/conventional_commits"
20
+ spec.metadata["changelog_uri"] = "https://github.com/Swift-Gurus/conventional_commits"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
27
+ end
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ # Uncomment to register a new dependency of your gem
34
+ spec.add_dependency "open3"
35
+ spec.add_dependency "thor"
36
+ spec.add_dependency "yaml"
37
+
38
+ spec.add_development_dependency "aruba"
39
+ spec.add_development_dependency "cucumber"
40
+ spec.add_development_dependency "simplecov"
41
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ class BranchNameGenerator
5
+ attr_reader :reader
6
+
7
+ def initialize(reader: reader = Configuration::BranchConfigurationReader.new)
8
+ @reader = reader
9
+ end
10
+
11
+ def generate_name_for(_input, path: Configuration::DEFAULT_CONFIGURATION_PATH)
12
+ configuration = reader.get_configuration(path:)
13
+ delimiters = find_delimiters_from_pattern(pattern: configuration.pattern)
14
+ input_order = transform_input_using_delimiters(_input, delimiters)
15
+
16
+ name = input_order.each_with_index.reduce("") do |acc, (e, i)|
17
+ acc + e + (delimiters[i] || (i + 1 == input_order.length ? "" : "-"))
18
+ end
19
+
20
+ name.downcase if configuration.lowercase == true
21
+ end
22
+
23
+ def branch_name_components(_input, path: Configuration::DEFAULT_CONFIGURATION_PATH)
24
+ configuration = reader.get_configuration(path:)
25
+ delimiters = find_delimiters_from_pattern(pattern: configuration.pattern)
26
+ received = _input
27
+ modified_input = _input
28
+ out = []
29
+ delimiters.each do |d|
30
+ splits = modified_input.split(d, 2)
31
+ out.push(splits[0]) if splits.length.positive?
32
+ modified_input = splits[1] || ""
33
+ end
34
+ out.push(modified_input) unless modified_input.empty?
35
+ if delimiters.length + 1 > out.length
36
+ raise ConventionalCommits::GenericError,
37
+ "The branch doesnt respect the template, expect #{delimiters.length} delimiters. Received: #{received}".strip
38
+ end
39
+
40
+ out
41
+ end
42
+
43
+ def is_valid_branch(_input, path: Configuration::DEFAULT_CONFIGURATION_PATH)
44
+ !branch_name_components(_input, path:).empty?
45
+ end
46
+
47
+ private
48
+
49
+ def find_delimiters_from_pattern(pattern: string)
50
+ delimiters = pattern.gsub(/<type>/, "<replace>")
51
+ .gsub(/<ticket>/, "<replace>")
52
+ .gsub(/<description>/, "<replace>")
53
+ .split("<replace>")
54
+ .reject { |str| str.empty? == true }
55
+ raise(GenericError, "Delimiters not found in pattern") if delimiters.empty?
56
+
57
+ delimiters
58
+ end
59
+
60
+ def transform_input_using_delimiters(_input, delimiters)
61
+ splits = _input.gsub(/_/, " ").scan(/\w+/)
62
+ validate_delimiters(delimiters, splits.length)
63
+ splits
64
+ end
65
+
66
+ def validate_delimiters(delimiters, number_of_words)
67
+ return unless number_of_words < delimiters.length + 1
68
+
69
+ raise(GenericError,
70
+ "Doesnt match the pattern expect at least #{delimiters.length + 1} words")
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module ConventionalCommits
6
+ class BranchNameCLI < Thor
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ desc "branch NAME", "formats the branch name based on the rules"
12
+ option :cfg_path, type: :string, required: false
13
+ def branch(_name)
14
+ cfg_path = options["cfg_path"] || Configuration::DEFAULT_CONFIGURATION_PATH
15
+ generator = ConventionalCommits::BranchNameGenerator.new
16
+ generated_name = generator.generate_name_for(_name.strip, path: cfg_path.strip)
17
+ Kernel.system("git branch #{generated_name}")
18
+ rescue StandardError => e
19
+ raise Thor::Error, e.message
20
+ end
21
+
22
+ desc "prepare_commit_msg", "Prepares commit message"
23
+ option :msg_path, type: :string, required: false
24
+ option :source, type: :string, required: false
25
+ option :cfg_path, type: :string, required: false
26
+ def prepare_commit_msg
27
+ source = options["source"] || ""
28
+ puts source
29
+ msg_path = options["msg_path"] || Configuration::DEFAULT_COMMIT_MSG_PATH
30
+ cfg_path = options["cfg_path"] || Configuration::DEFAULT_CONFIGURATION_PATH
31
+
32
+ generator = ConventionalCommits::CommitMessageGenerator.new
33
+ name = generator.prepare_message_template_for_type(type: source, cfg_path:, msg_file_path: msg_path)
34
+ File.write_to_file(msg_path, name)
35
+ rescue StandardError => e
36
+ raise Thor::Error, e.message
37
+ end
38
+
39
+ desc "validate_branch", "validates if the branch conforms to the rules"
40
+ option :cfg_path, type: :string, required: false
41
+ def validate_branch_name
42
+ cfg_path = options["cfg_path"] || Configuration::DEFAULT_CONFIGURATION_PATH
43
+ generator = ConventionalCommits::BranchNameGenerator.new
44
+ name = ConventionalCommits::Git.new.current_branch_name
45
+ generator.is_valid_branch(name.strip, path: cfg_path.strip)
46
+ rescue StandardError => e
47
+ raise Thor::Error, e.message
48
+ end
49
+
50
+ desc "validate_commit_msg", "validates if the branch conforms to the rules"
51
+ option :msg_path, type: :string, required: false
52
+ option :msg, type: :string, required: false
53
+ option :cfg_path, type: :string, required: false
54
+ def validate_commit_msg
55
+ msg_path = options["msg_path"] || Configuration::DEFAULT_COMMIT_MSG_PATH
56
+ cfg_path = options["cfg_path"] || Configuration::DEFAULT_CONFIGURATION_PATH
57
+ validator = ConventionalCommits::CommitMessageValidator.new
58
+ validator.validate_commit_msg_from_file(commit_msg_path: msg_path, cfg_path:)
59
+ rescue StandardError => e
60
+ raise Thor::Error, e.message
61
+ end
62
+
63
+ desc "install_hooks", "install all git hooks"
64
+ def install_hooks
65
+ installer = ConventionalCommits::Configuration::HooksInstaller.new
66
+ installer.install_all
67
+ rescue StandardError => e
68
+ raise Thor::Error, e.message
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ module ConventionalCommits
5
+ class CommitMessageGenerator
6
+ def prepare_template_message(custom_body: "", cfg_path: Configuration::DEFAULT_CONFIGURATION_PATH)
7
+ branch_name = Git.new.current_branch_name
8
+ main_config = Configuration::MainConfigurationReader.new.get_configuration(path: cfg_path)
9
+ configuration = main_config.branch
10
+ components = BranchNameGenerator.new.branch_name_components(branch_name, path: cfg_path)
11
+ type = (components[0] || "").downcase
12
+ ticket_number = components[1] || ""
13
+ description = components[2] || ""
14
+ description = description.gsub(/-/, " ").gsub(/_/, " ")
15
+
16
+ unless main_config.type.is_allowed(type)
17
+ raise ConventionalCommits::GenericError,
18
+ "The type #{type} is not allowed. Allowed types #{main_config.type.all_types}"
19
+ end
20
+ type = "#{main_config.type.main_type(type)}: #{description}".strip
21
+
22
+ body = custom_body.strip.empty? ? Configuration::DEFAULT_COMMIT_BODY_TEMPLATE : custom_body.strip
23
+ footer = "Ref: ##{configuration.ticket_prefix}#{ticket_number}".strip
24
+ "#{type}\n\n#{body}\n\n#{footer}"
25
+ end
26
+
27
+ def prepare_message_template_for_type(type: "",
28
+ cfg_path: Configuration::DEFAULT_CONFIGURATION_PATH,
29
+ msg_file_path: Configuration::DEFAULT_COMMIT_MSG_PATH)
30
+ prepare_template_message(cfg_path:) unless should_try_to_parse_msg_from_file(source: type)
31
+
32
+ parser = CommitMessageParser.new
33
+ components = parser.message_components(commit_msg_path: msg_file_path, cfg_path:)
34
+ prepare_template_message(custom_body: components[:body], cfg_path:)
35
+ rescue StandardError
36
+ data = File.read_file(msg_file_path).to_s
37
+ prepare_template_message(custom_body: data, cfg_path:)
38
+ end
39
+
40
+ private
41
+
42
+ def should_try_to_parse_msg_from_file(source: "")
43
+ skippable_sources.include?(source)
44
+ end
45
+
46
+ def skippable_sources
47
+ %w[message merge squash]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ class CommitMessageParser
5
+ def message_components(commit_msg_path: Configuration::DEFAULT_COMMIT_MSG_PATH,
6
+ cfg_path: Configuration::DEFAULT_CONFIGURATION_PATH)
7
+ raise GenericError, "Commit Message File is not created" unless File.exist?(commit_msg_path)
8
+
9
+ data = File.read_file(commit_msg_path)
10
+ message_components_from_string(commit_msg: data.to_s, cfg_path:)
11
+ end
12
+
13
+ def message_components_from_string(commit_msg: "",
14
+ cfg_path: Configuration::DEFAULT_CONFIGURATION_PATH)
15
+ msg_components = commit_msg.split "\n"
16
+ if msg_components.length < 5
17
+ raise GenericError, "Commit Message Doesnt respect the spec, expect to have subject, body and footer"
18
+ end
19
+
20
+ main_config = Configuration::MainConfigurationReader.new.get_configuration(path: cfg_path)
21
+
22
+ subject_components = msg_components[0].split(":").map(&:strip)
23
+ if subject_components.length < 2
24
+ raise raise GenericError,
25
+ "Subject doesnt respect the format"
26
+ end
27
+ components = { type: "", title: "", body: "" }
28
+ components[:type] = subject_components[0]
29
+ components[:title] = subject_components[1]
30
+ components[:body] = msg_components[2]
31
+
32
+ unless main_config.type.is_allowed(components[:type])
33
+ raise raise GenericError,
34
+ "Commit Type is not in the config yaml"
35
+ end
36
+
37
+ components
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ class CommitMessageValidator
5
+ def validate_commit_msg_from_file(commit_msg_path: Configuration::DEFAULT_COMMIT_MSG_PATH,
6
+ cfg_path: Configuration::DEFAULT_CONFIGURATION_PATH)
7
+ parser = CommitMessageParser.new
8
+ components = parser.message_components(commit_msg_path:, cfg_path:)
9
+
10
+ raise GenericError, "The Message is Invalid" if components.empty?
11
+
12
+ if components[:body].include?(Configuration::DEFAULT_COMMIT_BODY_TEMPLATE)
13
+ raise GenericError,
14
+ "Body contains template"
15
+ end
16
+
17
+ puts "Commit message is valid"
18
+ true
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ class BranchConfigurationReader
6
+ def get_configuration(path: Configuration::DEFAULT_CONFIGURATION_PATH)
7
+ reader = MainConfigurationReader.new
8
+ config = reader.get_configuration(path:)
9
+ config.branch
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ module ConventionalCommits
5
+ module Configuration
6
+ class MainConfigurationReader
7
+ def get_configuration(path: Configuration::DEFAULT_CONFIGURATION_PATH)
8
+ raise GenericError, "Path is empty" if path.empty?
9
+ raise GenericError, "File not found" unless File.exist?(path)
10
+
11
+ file = File.open(path)
12
+ config = YAML.load(file)
13
+ MainConfiguration.new(config)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ module ConventionalCommits
20
+ class GenericError < StandardError
21
+ def initialize(msg = "This is a custom exception", exception_type = "custom")
22
+ @exception_type = exception_type
23
+ super(msg)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ DEFAULT_CONFIGURATION_PATH = ".conventional_commit/config.yaml"
6
+ DEFAULT_COMMIT_MSG_PATH = ".git/COMMIT_EDITMSG"
7
+ DEFAULT_COMMIT_BODY_TEMPLATE = "[Describe your work, and put an empty string after]"
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "conventional_commits/version"
4
+
5
+ module Conventional§Commits
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
data/lib/git/git.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ class Git
5
+ def current_branch_name
6
+ run_system_command "git symbolic-ref --short HEAD"
7
+ end
8
+
9
+ def set_commit_msg(_msg)
10
+ run_system_command "git symbolic-ref --short HEAD"
11
+ end
12
+
13
+ def run_system_command(_cmd)
14
+ Open3.popen3(_cmd) { |_stdin, stdout, _stderr, _wait_thr| stdout.read }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ class File
5
+ def self.write_to_file(path, data)
6
+ open_file_with_data(path, data)
7
+ end
8
+
9
+ def self.delete_directory(path: string)
10
+ return unless Dir.exist?(path)
11
+
12
+ FileUtils.rm_r(path)
13
+ end
14
+
15
+ def self.delete_file(path: string)
16
+ return unless exist?(path)
17
+
18
+ delete(path)
19
+ end
20
+
21
+ def self.create_directory(path: string)
22
+ FileUtils.mkdir_p(path) unless Dir.exist?(path)
23
+ end
24
+
25
+ def self.create_new_file_with_data(path, data)
26
+ File.open(path, "w") do |f|
27
+ f.write(data)
28
+ end
29
+ end
30
+
31
+ def self.read_file(path)
32
+ File.open(path, "r") do |f|
33
+ data = f.read.to_s
34
+ return data || ""
35
+ end
36
+ end
37
+
38
+ def self.open_file_with_data(path, data)
39
+ File.open(path, "w") do |f|
40
+ f.write(data)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module ConventionalCommits
6
+ module Configuration
7
+ class HooksInstaller
8
+ def install_prepare_commit_msg
9
+ cmd = (common_hooks + [
10
+ cli_command(prepare_commit_cli)
11
+ ])
12
+ .join("\n")
13
+ create_file_and_make_executable(path: "prepare-commit-msg", data: cmd)
14
+ end
15
+
16
+ def install_pre_commit
17
+ cmd = common_hooks
18
+ .join("\n")
19
+ create_file_and_make_executable(path: "pre-commit", data: cmd)
20
+ end
21
+
22
+ def install_commit_msg
23
+ cmd = (common_hooks + [cli_command(validate_commit_msg)]).join("\n")
24
+ create_file_and_make_executable(path: "commit-msg", data: cmd)
25
+ end
26
+
27
+ def install_all
28
+ install_pre_commit
29
+ install_commit_msg
30
+ install_prepare_commit_msg
31
+ end
32
+
33
+ def create_file_and_make_executable(path:, data:)
34
+ File.create_directory(path: hook_directory)
35
+ full_path = "#{hook_directory}/#{path}"
36
+ File.create_new_file_with_data(full_path, data)
37
+ FileUtils.chmod("+x", full_path)
38
+ end
39
+
40
+ def initial_lines
41
+ [start_line,
42
+ ruby_eval]
43
+ end
44
+
45
+ def hook_directory
46
+ ".git/hooks"
47
+ end
48
+
49
+ def common_hooks
50
+ initial_lines +
51
+ [
52
+ cli_command("validate_branch_name")
53
+ ]
54
+ end
55
+
56
+ def cli_command(cmd)
57
+ "bundle exec bin/conventional_commit #{cmd}"
58
+ end
59
+
60
+ def ruby_eval
61
+ "eval \"$(rbenv init -)\""
62
+ end
63
+
64
+ def start_line
65
+ "#!/bin/sh"
66
+ end
67
+
68
+ def prepare_commit_cli
69
+ "prepare_commit_msg --msg_path $1"
70
+ end
71
+
72
+ def validate_commit_msg
73
+ "validate_commit_msg --msg_path $1"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ # Configuration for branch name policies
6
+ class BranchConfiguration
7
+ attr_reader :ticket_prefix, :lowercase, :pattern
8
+
9
+ def initialize(options = {})
10
+ @ticket_prefix = options["ticket_prefix"] || ""
11
+ @lowercase = options["lowercase"] || true
12
+ @pattern = options["pattern"] || "<type>/<ticket>/<description>"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ # Configuration for branch name policies
6
+ class CommitConfiguration
7
+ attr_reader :ticket_prefix, :lowercase, :include_ticket_number
8
+
9
+ def initialize(ticket:, lowercase:, include_ticket_number:)
10
+ @ticket_prefix = ticket
11
+ @lowercase = lowercase
12
+ @include_ticket_number = include_ticket_number
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ # Configuration for branch name policies
6
+ class MainConfiguration
7
+ attr_reader :release,
8
+ :branch,
9
+ :type
10
+
11
+ def initialize(options = {})
12
+ @release = ReleaseConfiguration.new(options["release"] || {})
13
+ @branch = BranchConfiguration.new(options["branch"] || {})
14
+ @type = TypeConfiguration.new(options["type"] || {})
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ # Configuration for branch name policies
6
+ class ReleaseConfiguration
7
+ attr_reader :rules
8
+
9
+ def initialize(options = {})
10
+ @rules = (options["rules"] || []).map { |options| ReleaseRule.new(options) }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ # Configuration for branch name policies
6
+ class ReleaseRule
7
+ attr_reader :types, :version
8
+
9
+ def initialize(options = {})
10
+ @types = options["types"] || []
11
+ @version = options["version"] || "none"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ module Configuration
5
+ # Configuration for branch name policies
6
+ class TypeConfiguration
7
+ attr_reader :options
8
+
9
+ def initialize(o = {})
10
+ @options = o
11
+ end
12
+
13
+ def is_allowed(type)
14
+ all_types.include?(type)
15
+ end
16
+
17
+ def main_type(_type)
18
+ match = ""
19
+
20
+ options.each do |key, array|
21
+ match = key if array.include?(_type)
22
+ end
23
+ match
24
+ end
25
+
26
+ def all_types
27
+ options.keys + options.values.flat_map { |e| e }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ module ConventionalCommit
2
+ class BranchNameGenerator
3
+ include ConventionalCommits::Configuration
4
+ @reader: ConventionalCommits::Configuration::BranchConfigurationReader
5
+
6
+ attr_reader reader: ConventionalCommits::Configuration::BranchConfigurationReader
7
+
8
+ def generate_name_for: -> string
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module ConventionalCommits
2
+ class BranchNameCLI
3
+ def self.exit_on_failure?: -> bool
4
+ def branch: -> void
5
+
6
+ def prepare_commit_msg: -> void
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module ConventionalCommits
2
+ class BranchNameGenerator
3
+ @reader: Configuration::BranchConfigurationReader
4
+
5
+ attr_reader reader: Configuration::BranchConfigurationReader
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module ConventionalCommits
2
+ class CommitMessage
3
+ def prepare_template_message: -> String
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module ConventionalCommits
2
+ module Configuration
3
+ DEFAULT_CONFIGURATION_PATH: string
4
+ DEFAULT_COMMIT_MSG_PATH: string
5
+ DEFAULT_COMMIT_BODY_TEMPLATE: string
6
+ class BranchConfiguration
7
+ @lowercase: bool
8
+ @pattern: string
9
+ @ticket_prefix: string
10
+
11
+ attr_reader pattern: bool
12
+ attr_reader lowercase: bool
13
+ attr_reader ticket_prefix: string
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module ConventionalCommits
2
+ module Configuration
3
+ class BranchConfigurationReader
4
+ def get_configuration: -> BranchConfiguration
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ module ConventionalCommits
2
+ module Configuration
3
+
4
+ class MainConfiguration
5
+ @release: ReleaseConfiguration
6
+ @branch: BranchConfiguration
7
+ @type: TypeConfiguration
8
+
9
+ attr_reader branch: BranchConfiguration
10
+ attr_reader release: ReleaseConfiguration
11
+ attr_reader type: TypeConfiguration
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module ConventionalCommits
2
+ module Configuration
3
+ class MainConfigurationReader
4
+ def get_configuration: -> MainConfiguration
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module ConventionalCommits
2
+ module Configuration
3
+ class ReleaseConfiguration
4
+ @rules: [ReleaseRule]
5
+
6
+ attr_reader rules: [ReleaseRule]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module ConventionalCommits
2
+ module Configuration
3
+ class ReleaseRule
4
+ attr_reader types: [string]
5
+ attr_reader version: string
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module ConventionalCommits
2
+ module Configuration
3
+ class TypeConfiguration
4
+ attr_reader options: {}
5
+
6
+ def is_allowed: -> bool
7
+
8
+ def main_type: -> string
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module ConventionalCommits
2
+ class Git
3
+ def current_branch_name: -> string
4
+
5
+ def run_system_command: -> string
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module ConventionalCommits
2
+ class SystemCaller
3
+ def run_command: -> void
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module ConventionalCommits
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/sig/file.rbs ADDED
@@ -0,0 +1,7 @@
1
+ class File
2
+ def self.create_directory: -> void
3
+
4
+ def self.delete_directory: -> void
5
+
6
+ def self.write_to_file: -> void
7
+ end
@@ -0,0 +1,3 @@
1
+ class GenericError
2
+ @exception_type: string
3
+ end
@@ -0,0 +1,9 @@
1
+ module Support
2
+ module Mocks
3
+ class FileMockOperations
4
+ def self.update_pattern_in_config: -> void
5
+
6
+ attr_reader mocks: MockData
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Support
2
+ module Mocks
3
+ class MockData
4
+ attr_accessor working_directory: string
5
+
6
+ attr_accessor branch_name: string
7
+ attr_accessor configuration: string
8
+ attr_accessor directory: string
9
+ attr_accessor file: string
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: conventional_commits
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Swift-Gurus
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: open3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yaml
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aruba
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cucumber
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Enforces conventional commits specs.
98
+ email: alexei.hmelevski@gmail.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".rspec"
104
+ - ".rubocop.yml"
105
+ - Gemfile
106
+ - Gemfile.lock
107
+ - README.md
108
+ - Rakefile
109
+ - conventional_commits.gemspec
110
+ - lib/branch/branch_name_generator.rb
111
+ - lib/cli/branch_name_cli.rb
112
+ - lib/commit/commit_message_generator.rb
113
+ - lib/commit/commit_message_parser.rb
114
+ - lib/commit/commit_message_validator.rb
115
+ - lib/configuration.rb
116
+ - lib/configuration/branch_configuration_reader.rb
117
+ - lib/configuration/main_configuration_reader.rb
118
+ - lib/conventional_commits.rb
119
+ - lib/conventional_commits/version.rb
120
+ - lib/git/git.rb
121
+ - lib/helpers/file_extension.rb
122
+ - lib/hooks/hooks_installer.rb
123
+ - lib/models/branch_configuration.rb
124
+ - lib/models/commit_configuration.rb
125
+ - lib/models/main_configuration.rb
126
+ - lib/models/release_configuration.rb
127
+ - lib/models/release_rule.rb
128
+ - lib/models/type_configuration.rb
129
+ - sig/conventional_commit/branch_name_generator.rbs
130
+ - sig/conventional_commits.rbs
131
+ - sig/conventional_commits/branch_name_cli.rbs
132
+ - sig/conventional_commits/branch_name_generator.rbs
133
+ - sig/conventional_commits/commit_message.rbs
134
+ - sig/conventional_commits/configuration/branch_configuration.rbs
135
+ - sig/conventional_commits/configuration/branch_configuration_reader.rbs
136
+ - sig/conventional_commits/configuration/main_configuration.rbs
137
+ - sig/conventional_commits/configuration/main_configuration_reader.rbs
138
+ - sig/conventional_commits/configuration/release_configuration.rbs
139
+ - sig/conventional_commits/configuration/release_rule.rbs
140
+ - sig/conventional_commits/configuration/type_configuration.rbs
141
+ - sig/conventional_commits/git.rbs
142
+ - sig/conventional_commits/system_caller.rbs
143
+ - sig/file.rbs
144
+ - sig/generic_error.rbs
145
+ - sig/support/mocks/file_mock_operations.rbs
146
+ - sig/support/mocks/mock_data.rbs
147
+ homepage: https://github.com/Swift-Gurus/conventional_commits
148
+ licenses: []
149
+ metadata:
150
+ allowed_push_host: https://rubygems.org
151
+ homepage_uri: https://github.com/Swift-Gurus/conventional_commits
152
+ source_code_uri: https://github.com/Swift-Gurus/conventional_commits
153
+ changelog_uri: https://github.com/Swift-Gurus/conventional_commits
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: 3.1.0
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubygems_version: 3.3.7
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Make your commit unified.
173
+ test_files: []