diff --git a/Cargo.lock b/Cargo.lock index 09f6157..76b4352 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,8 +200,8 @@ dependencies = [ [[package]] name = "codeowners" -version = "0.2.7" -source = "git+https://github.com/rubyatscale/codeowners-rs.git?tag=v0.2.7#ca456be254a81e9a84a87e605c00da66e6c644ec" +version = "0.2.14" +source = "git+https://github.com/rubyatscale/codeowners-rs.git?tag=v0.2.14#55832fc2bc34d961571fdf14e1a02761590aa2be" dependencies = [ "clap", "clap_derive", diff --git a/ext/code_ownership/Cargo.toml b/ext/code_ownership/Cargo.toml index 10f1932..5ec1f48 100644 --- a/ext/code_ownership/Cargo.toml +++ b/ext/code_ownership/Cargo.toml @@ -17,7 +17,7 @@ rb-sys = { version = "0.9.111", features = [ magnus = { version = "0.7.1" } serde = { version = "1.0.219", features = ["derive"] } serde_magnus = "0.9.0" -codeowners = { git = "https://github.com/rubyatscale/codeowners-rs.git", tag = "v0.2.7" } +codeowners = { git = "https://github.com/rubyatscale/codeowners-rs.git", tag = "v0.2.14" } [dev-dependencies] rb-sys = { version = "0.9.117", features = [ diff --git a/ext/code_ownership/src/lib.rs b/ext/code_ownership/src/lib.rs index ca061d1..c44297b 100644 --- a/ext/code_ownership/src/lib.rs +++ b/ext/code_ownership/src/lib.rs @@ -9,6 +9,7 @@ use serde_magnus::serialize; pub struct Team { pub team_name: String, pub team_config_yml: String, + pub reasons: Vec, } fn for_team(team_name: String) -> Result { @@ -20,16 +21,24 @@ fn for_team(team_name: String) -> Result { fn for_file(file_path: String) -> Result, Error> { let run_config = build_run_config(); - match runner::team_for_file(&run_config, &file_path) { - Ok(Some(team_rs)) => { + match runner::file_owner_for_file(&run_config, &file_path) { + Ok(owner) => { + if let Some(owner) = owner { let team = Team { - team_name: team_rs.name, - team_config_yml: team_rs.path.to_string_lossy().to_string(), + team_name: owner.team.name, + team_config_yml: owner.team_config_file_path.to_string(), + reasons: owner + .sources + .iter() + .map(|source| source.to_string()) + .collect(), }; let serialized: Value = serialize(&team)?; Ok(Some(serialized)) + } else { + Ok(None) + } } - Ok(None) => Ok(None), Err(e) => Err(Error::new( magnus::exception::runtime_error(), e.to_string(), diff --git a/lib/code_ownership.rb b/lib/code_ownership.rb index f007cb7..44b91fa 100644 --- a/lib/code_ownership.rb +++ b/lib/code_ownership.rb @@ -11,6 +11,7 @@ require 'code_ownership/private/file_path_finder' require 'code_ownership/private/file_path_team_cache' require 'code_ownership/private/team_finder' +require 'code_ownership/private/for_file_output_builder' require 'code_ownership/cli' begin @@ -44,6 +45,11 @@ def for_file(file) Private::TeamFinder.for_file(file) end + sig { params(file: String).returns(T.nilable(T::Hash[Symbol, String])) } + def for_file_verbose(file) + ::RustCodeOwners.for_file(file) + end + sig { params(team: T.any(CodeTeams::Team, String)).returns(T::Array[String]) } def for_team(team) team = T.must(CodeTeams.find(team)) if team.is_a?(String) diff --git a/lib/code_ownership/cli.rb b/lib/code_ownership/cli.rb index de60f7d..79d776e 100644 --- a/lib/code_ownership/cli.rb +++ b/lib/code_ownership/cli.rb @@ -95,6 +95,10 @@ def self.for_file(argv) options[:json] = true end + opts.on('--verbose', 'Output verbose information') do + options[:verbose] = true + end + opts.on('--help', 'Shows this prompt') do puts opts exit @@ -107,24 +111,7 @@ def self.for_file(argv) raise "Please pass in one file. Use `#{EXECUTABLE} for_file --help` for more info" end - team = CodeOwnership.for_file(files.first) - - team_name = team&.name || 'Unowned' - team_yml = team&.config_yml || 'Unowned' - - if options[:json] - json = { - team_name: team_name, - team_yml: team_yml - } - - puts json.to_json - else - puts <<~MSG - Team: #{team_name} - Team YML: #{team_yml} - MSG - end + puts CodeOwnership::Private::ForFileOutputBuilder.build(file_path: files.first, json: !!options[:json], verbose: !!options[:verbose]) end def self.for_team(argv) diff --git a/lib/code_ownership/code_ownership.bundle b/lib/code_ownership/code_ownership.bundle index 60d6e9d..c51e6bc 100755 Binary files a/lib/code_ownership/code_ownership.bundle and b/lib/code_ownership/code_ownership.bundle differ diff --git a/lib/code_ownership/private/for_file_output_builder.rb b/lib/code_ownership/private/for_file_output_builder.rb new file mode 100644 index 0000000..d667091 --- /dev/null +++ b/lib/code_ownership/private/for_file_output_builder.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# typed: strict + +module CodeOwnership + module Private + class ForFileOutputBuilder + extend T::Sig + private_class_method :new + + sig { params(file_path: String, json: T::Boolean, verbose: T::Boolean).void } + def initialize(file_path:, json:, verbose:) + @file_path = file_path + @json = json + @verbose = verbose + end + + sig { params(file_path: String, json: T::Boolean, verbose: T::Boolean).returns(String) } + def self.build(file_path:, json:, verbose:) + new(file_path: file_path, json: json, verbose: verbose).build + end + + UNOWNED_OUTPUT = T.let( + { + team_name: 'Unowned', + team_yml: 'Unowned' + }, + T::Hash[Symbol, T.untyped] + ) + + sig { returns(String) } + def build + result_hash = @verbose ? build_verbose : build_terse + + return result_hash.to_json if @json + + build_message_for(result_hash) + end + + private + + sig { returns(T::Hash[Symbol, T.untyped]) } + def build_verbose + result = CodeOwnership.for_file_verbose(@file_path) + return UNOWNED_OUTPUT if result.nil? + + { + team_name: result[:team_name], + team_yml: result[:team_config_yml], + description: result[:reasons] + } + end + + sig { returns(T::Hash[Symbol, T.untyped]) } + def build_terse + team = CodeOwnership.for_file(@file_path) + + if team.nil? + UNOWNED_OUTPUT + else + { + team_name: team.name, + team_yml: team.config_yml + } + end + end + + sig { params(result_hash: T::Hash[Symbol, T.untyped]).returns(String) } + def build_message_for(result_hash) + messages = ["Team: #{result_hash[:team_name]}", "Team YML: #{result_hash[:team_yml]}"] + description_list = T.let(Array(result_hash[:description]), T::Array[String]) + messages << build_description_message(description_list) unless description_list.empty? + messages.last << "\n" + messages.join("\n") + end + + sig { params(reasons: T::Array[String]).returns(String) } + def build_description_message(reasons) + "Description:\n- #{reasons.join("\n-")}" + end + end + end +end diff --git a/lib/code_ownership/version.rb b/lib/code_ownership/version.rb index 234aaae..b68d517 100644 --- a/lib/code_ownership/version.rb +++ b/lib/code_ownership/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CodeOwnership - VERSION = '2.0.0-1' + VERSION = '2.0.0-2' end diff --git a/spec/lib/code_ownership/cli_spec.rb b/spec/lib/code_ownership/cli_spec.rb index 30aa2b5..9950574 100644 --- a/spec/lib/code_ownership/cli_spec.rb +++ b/spec/lib/code_ownership/cli_spec.rb @@ -176,6 +176,20 @@ def initialize end end + context 'when run with --verbose' do + let(:argv) { ['for_file', 'app/services/my_file.rb', '--verbose'] } + + it 'outputs the team info in human readable format' do + expect(CodeOwnership::Cli).to receive(:puts).with(<<~MSG) + Team: My Team + Team YML: config/teams/my_team.yml + Description: + - Owner specified in Team YML as an owned_glob `app/**/*.rb` + MSG + subject + end + end + context 'when run with no files' do let(:argv) { ['for_file'] } @@ -205,6 +219,19 @@ def initialize expect(CodeOwnership::Cli).to receive(:puts).with(json.to_json) subject end + + context 'when run with --verbose' do + let(:argv) { ['for_file', '--json', '--verbose', 'app/services/my_file.rb'] } + it 'outputs JSONified information to the console' do + json = { + team_name: 'My Team', + team_yml: 'config/teams/my_team.yml', + description: ['Owner specified in Team YML as an owned_glob `app/**/*.rb`'] + } + expect(CodeOwnership::Cli).to receive(:puts).with(json.to_json) + subject + end + end end context 'when run with no files' do @@ -235,6 +262,19 @@ def initialize MSG subject end + + context 'when run with --verbose' do + let(:argv) { ['for_file', 'app/services/unowned.rb', '--verbose'] } + + it 'prints Unowned' do + allow(CodeOwnership).to receive(:for_file_verbose).and_return(nil) + expect(CodeOwnership::Cli).to receive(:puts).with(<<~MSG) + Team: Unowned + Team YML: Unowned + MSG + subject + end + end end context 'with --help' do