File indexing completed on 2024-03-24 16:47:07

0001 # frozen_string_literal: true
0002 #
0003 # Copyright (C) 2015-2016 Harald Sitter <sitter@kde.org>
0004 #
0005 # This library is free software; you can redistribute it and/or
0006 # modify it under the terms of the GNU Lesser General Public
0007 # License as published by the Free Software Foundation; either
0008 # version 2.1 of the License, or (at your option) version 3, or any
0009 # later version accepted by the membership of KDE e.V. (or its
0010 # successor approved by the membership of KDE e.V.), which shall
0011 # act as a proxy defined in Section 6 of version 3 of the license.
0012 #
0013 # This library is distributed in the hope that it will be useful,
0014 # but WITHOUT ANY WARRANTY; without even the implied warranty of
0015 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016 # Lesser General Public License for more details.
0017 #
0018 # You should have received a copy of the GNU Lesser General Public
0019 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0020 
0021 require_relative 'architecturequalifier'
0022 
0023 module Debian
0024   # A package relationship.
0025   class Relationship
0026     # Name of the package related to
0027     attr_reader :name
0028     # Architecture qualification of the package (foo:amd64)
0029     attr_accessor :architecture
0030     # Version relationship operator (>=, << etc.)re
0031     attr_accessor :operator
0032     # Related to version of the named package
0033     attr_accessor :version
0034     # Next OR'd dep if any
0035     attr_accessor :next
0036 
0037     # architecture restriction for package
0038     # [architecture restriction] https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-spec
0039     attr_accessor :architectures
0040 
0041     # Not public because not needed for now.
0042     # <build profile restriction> https://wiki.debian.org/BuildProfileSpec
0043     # attr_accessor :profiles
0044 
0045     # Borrowed from Deps.pm. Added capture group names:
0046     #   [name, architecture, operator, architectures, restrictions]
0047     REGEX = /
0048       ^\s*                           # skip leading whitespace
0049        (?<name>
0050         [a-zA-Z0-9][a-zA-Z0-9+.-]*)  # package name
0051        (?:                           # start of optional part
0052          :                           # colon for architecture
0053          (?<architecture>
0054           [a-zA-Z0-9][a-zA-Z0-9-]*)  # architecture name
0055        )?                            # end of optional part
0056        (?:                           # start of optional part
0057          \s* \(                      # open parenthesis for version part
0058          \s* (?<operator>
0059               <<|<=|=|>=|>>|[<>])    # relation part
0060          \s* (?<version>.*?)         # do not attempt to parse version
0061          \s* \)                      # closing parenthesis
0062        )?                            # end of optional part
0063        (?:                           # start of optional architecture
0064          \s* \[                      # open bracket for architecture
0065          \s* (?<architectures>
0066               .*?)                   # don't parse architectures now
0067          \s* \]                      # closing bracket
0068        )?                            # end of optional architecture
0069        (?:                           # start of optional restriction
0070          \s* <                       # open bracket for restriction
0071          \s* (?<profiles>
0072               .*)                    # do not parse restrictions now
0073          \s* >                       # closing bracket
0074        )?                            # end of optional restriction
0075        \s*$                          # trailing spaces at end
0076      /x
0077 
0078     def initialize(string)
0079       string = string.strip
0080       return if string.empty?
0081 
0082       first, everything_else = string.split('|', 2)
0083 
0084       @next = Debian::Relationship.new(everything_else) if everything_else
0085 
0086       match = first.match(REGEX)
0087       if match
0088         process_match(match)
0089       else
0090         @name = string
0091       end
0092     end
0093 
0094     def substvar?
0095       @name.start_with?('${') && @name.end_with?('}')
0096     end
0097 
0098     def <=>(other)
0099       if substvar? || other.substvar? # any is a substvar
0100         return -1 unless other.substvar? # substvar always looses
0101         return 1 unless substvar? # non-substvar always wins
0102         return substvarcmp(other) # substvars are compared among themself
0103       end
0104       @name <=> other.name
0105     end
0106 
0107     def to_s
0108       output = @name
0109       output += f(':%s', @architecture)
0110       output += f(' (%s %s)', @operator, @version)
0111       output += f(' [%s]', @architectures)
0112       output += f(' <%s>', @profiles)
0113       output += f(' | %s', @next) if @next
0114       output
0115     end
0116 
0117     private
0118 
0119     def substvarcmp(other)
0120       ours = @name.gsub('${', '').tr('}', '')
0121       theirs = other.name.gsub('${', '').tr('}', '')
0122       ours <=> theirs
0123     end
0124 
0125     def f(str, *params)
0126       return '' if params.any?(&:nil?)
0127       format(str, *params)
0128     end
0129 
0130     def process_match(match)
0131       match.names.each do |name|
0132         data = match[name]
0133         data.strip! if data
0134         next unless data
0135         data = ArchitectureQualifier.new(data) if name == 'architectures'
0136         instance_variable_set("@#{name}".to_sym, data)
0137       end
0138     end
0139   end
0140 end