| 
									
										
										
										
											2025-01-06 00:12:03 +00:00
										 |  |  | # typed: strict | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require "system_command" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Homebrew | 
					
						
							|  |  |  |   # Representation of a macOS bundle version, commonly found in `Info.plist` files. | 
					
						
							|  |  |  |   class BundleVersion | 
					
						
							| 
									
										
										
										
											2021-03-28 04:15:27 +02:00
										 |  |  |     include Comparable | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |     extend SystemCommand::Mixin | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-15 15:21:14 +01:00
										 |  |  |     sig { params(info_plist_path: Pathname).returns(T.nilable(T.attached_class)) } | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |     def self.from_info_plist(info_plist_path) | 
					
						
							|  |  |  |       plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", info_plist_path]).plist | 
					
						
							| 
									
										
										
										
											2021-04-04 03:00:34 +02:00
										 |  |  |       from_info_plist_content(plist) | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-04 03:00:34 +02:00
										 |  |  |     sig { params(plist: T::Hash[String, T.untyped]).returns(T.nilable(T.attached_class)) } | 
					
						
							|  |  |  |     def self.from_info_plist_content(plist) | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |       short_version = plist["CFBundleShortVersionString"].presence | 
					
						
							|  |  |  |       version = plist["CFBundleVersion"].presence | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       new(short_version, version) if short_version || version | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-15 15:21:14 +01:00
										 |  |  |     sig { params(package_info_path: Pathname).returns(T.nilable(T.attached_class)) } | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |     def self.from_package_info(package_info_path) | 
					
						
							| 
									
										
										
										
											2021-06-25 18:42:59 -04:00
										 |  |  |       require "rexml/document" | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 18:42:59 -04:00
										 |  |  |       xml = REXML::Document.new(package_info_path.read) | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 18:42:59 -04:00
										 |  |  |       bundle_version_bundle = xml.get_elements("//pkg-info//bundle-version//bundle").first | 
					
						
							|  |  |  |       bundle_id = bundle_version_bundle["id"] if bundle_version_bundle | 
					
						
							|  |  |  |       return if bundle_id.blank? | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 18:42:59 -04:00
										 |  |  |       bundle = xml.get_elements("//pkg-info//bundle").find { |b| b["id"] == bundle_id } | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |       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) | 
					
						
							| 
									
										
										
										
											2023-05-08 05:38:13 +02:00
										 |  |  |       # Remove version from short version, if present. | 
					
						
							|  |  |  |       short_version = short_version&.sub(/\s*\(#{Regexp.escape(version)}\)\Z/, "") if version | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-06 00:12:03 +00:00
										 |  |  |       @short_version = T.let(short_version.presence, T.nilable(String)) | 
					
						
							|  |  |  |       @version = T.let(version.presence, T.nilable(String)) | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return if @short_version || @version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       raise ArgumentError, "`short_version` and `version` cannot both be `nil` or empty" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-06 00:12:03 +00:00
										 |  |  |     sig { params(other: BundleVersion).returns(T.any(Integer, NilClass)) } | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |     def <=>(other) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:06:24 +02:00
										 |  |  |       return super unless instance_of?(other.class) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       make_version = ->(v) { v ? Version.new(v) : Version::NULL } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       version = self.version.then(&make_version) | 
					
						
							|  |  |  |       other_version = other.version.then(&make_version) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       difference = version <=> other_version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # If `version` is equal or cannot be compared, compare `short_version` instead. | 
					
						
							|  |  |  |       if difference.nil? || difference.zero? | 
					
						
							|  |  |  |         short_version = self.short_version.then(&make_version) | 
					
						
							|  |  |  |         other_short_version = other.short_version.then(&make_version) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-21 16:22:56 +02:00
										 |  |  |         short_version_difference = short_version <=> other_short_version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return short_version_difference unless short_version_difference.nil? | 
					
						
							| 
									
										
										
										
											2023-05-16 14:06:24 +02:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       difference | 
					
						
							| 
									
										
										
										
											2021-03-28 04:15:27 +02:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-06 00:12:03 +00:00
										 |  |  |     sig { params(other: BundleVersion).returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2021-03-28 04:15:27 +02:00
										 |  |  |     def ==(other) | 
					
						
							|  |  |  |       instance_of?(other.class) && short_version == other.short_version && version == other.version | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2021-03-28 04:15:27 +02:00
										 |  |  |     alias eql? == | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-15 17:11:18 +01:00
										 |  |  |     # Create a nicely formatted version (on a best effort basis). | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |     sig { returns(String) } | 
					
						
							|  |  |  |     def nice_version | 
					
						
							|  |  |  |       nice_parts.join(",") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(T::Array[String]) } | 
					
						
							|  |  |  |     def nice_parts | 
					
						
							| 
									
										
										
										
											2020-12-14 11:47:57 +01:00
										 |  |  |       short_version = self.short_version | 
					
						
							| 
									
										
										
										
											2020-12-15 15:21:14 +01:00
										 |  |  |       version = self.version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return [T.must(short_version)] if short_version == version | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 09:04:54 +01:00
										 |  |  |       [short_version, version].compact | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  |     end | 
					
						
							|  |  |  |     private :nice_parts | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |