Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BundleVersion class. #10029

Merged
merged 7 commits into from Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 92 additions & 0 deletions Library/Homebrew/bundle_version.rb
@@ -0,0 +1,92 @@
# typed: true
# frozen_string_literal: true

require "system_command"

module Homebrew
# Representation of a macOS bundle version, commonly found in `Info.plist` files.
#
# @api private
class BundleVersion
extend T::Sig

extend SystemCommand::Mixin

sig { params(info_plist_path: Pathname).returns(T.nilable(T.attached_class)) }
def self.from_info_plist(info_plist_path)
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", info_plist_path]).plist

short_version = plist["CFBundleShortVersionString"].presence
version = plist["CFBundleVersion"].presence

new(short_version, version) if short_version || version
end

sig { params(package_info_path: Pathname).returns(T.nilable(T.attached_class)) }
def self.from_package_info(package_info_path)
Homebrew.install_bundler_gems!
require "nokogiri"
reitermarkus marked this conversation as resolved.
Show resolved Hide resolved

xml = Nokogiri::XML(package_info_path.read)

bundle_id = xml.xpath("//pkg-info//bundle-version//bundle").first&.attr("id")
return unless bundle_id

bundle = xml.xpath("//pkg-info//bundle").find { |b| b["id"] == bundle_id }
return unless bundle

short_version = bundle["CFBundleShortVersionString"]
version = bundle["CFBundleVersion"]

new(short_version, version) if short_version || version
end

sig { returns(T.nilable(String)) }
attr_reader :short_version, :version

sig { params(short_version: T.nilable(String), version: T.nilable(String)).void }
def initialize(short_version, version)
@short_version = short_version.presence
@version = version.presence

return if @short_version || @version

raise ArgumentError, "`short_version` and `version` cannot both be `nil` or empty"
end

def <=>(other)
[version, short_version].map { |v| v&.yield_self(&Version.public_method(:new)) } <=>
[other.version, other.short_version].map { |v| v&.yield_self(&Version.public_method(:new)) }
end

# Create a nicely formatted version (on a best effor basis).
sig { returns(String) }
def nice_version
nice_parts.join(",")
end

sig { returns(T::Array[String]) }
def nice_parts
short_version = self.short_version
version = self.version

short_version = short_version&.delete_suffix("(#{version})") if version

return [T.must(short_version)] if short_version == version

if short_version && version
return [version] if version.match?(/\A\d+(\.\d+)+\Z/) && version.start_with?("#{short_version}.")
return [short_version] if short_version.match?(/\A\d+(\.\d+)+\Z/) && short_version.start_with?("#{version}.")

if short_version.match?(/\A\d+(\.\d+)*\Z/) && version.match?(/\A\d+\Z/)
return [short_version] if short_version.start_with?("#{version}.") || short_version.end_with?(".#{version}")

return [short_version, version]
end
end

[short_version, version].compact
end
private :nice_parts
end
end
29 changes: 29 additions & 0 deletions Library/Homebrew/test/bundle_version_spec.rb
@@ -0,0 +1,29 @@
# typed: false
# frozen_string_literal: true

require "bundle_version"

describe Homebrew::BundleVersion do
describe "#nice_version" do
expected_mappings = {
["1.2", nil] => "1.2",
[nil, "1.2.3"] => "1.2.3",
["1.2", "1.2.3"] => "1.2.3",
["1.2.3", "1.2"] => "1.2.3",
["1.2.3", "8312"] => "1.2.3,8312",
["2021", "2006"] => "2021,2006",
["1.0", "1"] => "1.0",
["1.0", "0"] => "1.0",
["1.2.3.4000", "4000"] => "1.2.3.4000",
["5", "5.0.45"] => "5.0.45",
["2.5.2(3329)", "3329"] => "2.5.2,3329",
}

expected_mappings.each do |(short_version, version), expected_version|
it "maps (#{short_version.inspect}, #{version.inspect}) to #{expected_version.inspect}" do
expect(described_class.new(short_version, version).nice_version)
.to eq expected_version
end
end
end
end
29 changes: 0 additions & 29 deletions Library/Homebrew/test/unversioned_cask_checker_spec.rb

This file was deleted.

55 changes: 3 additions & 52 deletions Library/Homebrew/unversioned_cask_checker.rb
@@ -1,6 +1,7 @@
# typed: true
# frozen_string_literal: true

require "bundle_version"
require "cask/cask"
require "cask/installer"

Expand Down Expand Up @@ -45,56 +46,6 @@ def single_pkg_cask?
pkgs.count == 1
end

sig { params(info_plist_path: Pathname).returns(T.nilable(String)) }
def self.version_from_info_plist(info_plist_path)
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", info_plist_path]).plist

short_version = plist["CFBundleShortVersionString"].presence
version = plist["CFBundleVersion"].presence

return decide_between_versions(short_version, version) if short_version && version
end

sig { params(package_info_path: Pathname).returns(T.nilable(String)) }
def self.version_from_package_info(package_info_path)
Homebrew.install_bundler_gems!
require "nokogiri"

xml = Nokogiri::XML(package_info_path.read)

bundle_id = xml.xpath("//pkg-info//bundle-version//bundle").first&.attr("id")
return unless bundle_id

bundle = xml.xpath("//pkg-info//bundle").find { |b| b["id"] == bundle_id }
return unless bundle

short_version = bundle["CFBundleShortVersionString"]
version = bundle["CFBundleVersion"]

return decide_between_versions(short_version, version) if short_version && version
end

sig do
params(short_version: T.nilable(String), version: T.nilable(String))
.returns(T.nilable(String))
end
def self.decide_between_versions(short_version, version)
return short_version if short_version == version

if short_version && version
return version if version.match?(/\A\d+(\.\d+)+\Z/) && version.start_with?("#{short_version}.")
return short_version if short_version.match?(/\A\d+(\.\d+)+\Z/) && short_version.start_with?("#{version}.")

if short_version.match?(/\A\d+(\.\d+)*\Z/) && version.match?(/\A\d+\Z/)
return short_version if short_version.start_with?("#{version}.") || short_version.end_with?(".#{version}")

return "#{short_version},#{version}"
end
end

short_version || version
end

sig { returns(T.nilable(String)) }
def guess_cask_version
if apps.empty? && pkgs.empty?
Expand All @@ -120,7 +71,7 @@ def guess_cask_version
end

info_plist_paths.each do |info_plist_path|
if (version = self.class.version_from_info_plist(info_plist_path))
if (version = BundleVersion.from_info_plist(info_plist_path)&.nice_version)
return version
end
end
Expand Down Expand Up @@ -149,7 +100,7 @@ def guess_cask_version

package_info_path = extract_dir/"PackageInfo"
if package_info_path.exist?
if (version = self.class.version_from_package_info(package_info_path))
if (version = BundleVersion.from_package_info(package_info_path)&.nice_version)
return version
end
elsif packages.count == 1
Expand Down