File indexing completed on 2024-04-14 15:22:19

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 'insensitive_hash/minimal'
0022 
0023 require_relative 'relationship'
0024 
0025 module Debian
0026   # Deb822 specification parser.
0027   class Deb822
0028     def parse_relationships(line)
0029       ret = []
0030       line.split(',').each do |string|
0031         rel_array = []
0032         string.split('|').each do |entry|
0033           r = Relationship.new(entry)
0034           next unless r.name # Invalid name, ignore this bugger.
0035           rel_array << r
0036         end
0037         ret << rel_array unless rel_array.empty?
0038       end
0039       ret
0040     end
0041 
0042     def parse_paragraph(lines, fields = {})
0043       mandatory_fields = fields[:mandatory] || []
0044       multiline_fields = fields[:multiline] || []
0045       foldable_fields = fields[:foldable] || []
0046       relationship_fields = fields[:relationship] || []
0047 
0048       current_header = nil
0049       data = InsensitiveHash.new
0050 
0051       while (line = lines.shift) && line && !line.strip.empty?
0052         next if line.start_with?('#') # Comment
0053 
0054         header_match = line.match(/^(\S+):(.*\n?)$/)
0055         unless header_match.nil?
0056           # 0 = full match
0057           # 1 = key match
0058           # 2 = value match
0059           key = header_match[1].lstrip
0060           value = header_match[2].lstrip
0061           current_header = key
0062           if foldable_fields.include?(key.downcase)
0063             # We do not care about whitespaces for folds, so strip everything.
0064             if relationship_fields.include?(key.downcase)
0065               value = parse_relationships(value)
0066             else
0067               value = [value.chomp(',').strip]
0068             end
0069           elsif multiline_fields.include?(key.downcase)
0070             # For multiline we want to preserve right hand side whitespaces.
0071             value
0072           else
0073             value.strip!
0074           end
0075           data[key] = value
0076           next
0077         end
0078 
0079         fold_match = line.match(/^\s+(.+\n)$/)
0080         unless fold_match.nil?
0081           # Folding value encountered -> append to header.
0082           # 0 full match
0083           # 1 value match
0084           value = fold_match[1].lstrip
0085 
0086           # Fold matches can either be proper RFC 5322 folds or
0087           # multiline continuations, latter wants to preserve
0088           # newlines and so forth.
0089           # The type is entirely dependent on what the header field is.
0090           if foldable_fields.include?(current_header.downcase)
0091             # We do not care about whitespaces for folds, so strip everything.
0092             if relationship_fields.include?(current_header.downcase)
0093               value = parse_relationships(value)
0094             else
0095               value = [value.strip]
0096             end
0097             data[current_header] += value
0098           elsif multiline_fields.include?(current_header.downcase)
0099             # For multiline we want to preserve right hand side whitespaces.
0100             data[current_header] << value
0101           else
0102             raise "A field is folding that is not allowed to #{current_header}"
0103           end
0104 
0105           next
0106         end
0107 
0108         # TODO: user defined fields
0109 
0110         raise "Paragraph parsing ran into an unknown line: '#{line}'"
0111       end
0112 
0113       # If the entire stanza was commented out we can end up with no data, it
0114       # is very sad.
0115       return nil if data.empty?
0116 
0117       mandatory_fields.each do |field|
0118         # TODO: this should really make a list and complain all at once or
0119         # something.
0120         raise "Missing mandatory field #{field}" unless data.include?(field)
0121       end
0122 
0123       data
0124     end
0125 
0126     def parse!
0127       raise 'Not implemented'
0128     end
0129 
0130     def dump_paragraph(data, fields = {})
0131       # mandatory_fields = fields[:mandatory] || []
0132       multiline_fields = fields[:multiline] || []
0133       # foldable_fields = fields[:foldable] || []
0134       relationship_fields = fields[:relationship] || []
0135 
0136       output = ''
0137       data.each do |field, value|
0138         key = "#{field}: "
0139         output += key
0140         field = field.downcase # normalize for include check
0141         if multiline_fields.include?(field)
0142           output += output_multiline(value)
0143         # elsif foldable_fields.include?(field)
0144           # output += output_foldable(value, key.length)
0145         elsif relationship_fields.include?(field)
0146           # relationships are always foldable
0147           output += output_relationship(value, key.length)
0148         else
0149           # FIXME: rstrip because multiline do not get their trailing newline
0150           #   stripped in parsing
0151           output += (value || value.rstrip)
0152         end
0153         output += "\n"
0154       end
0155       output
0156     end
0157 
0158     private
0159 
0160     def output_multiline(data)
0161       data = data.join("\n") if data.respond_to?(:join)
0162       data = data.to_s unless data.is_a?(String)
0163       data.gsub("\n", "\n ").chomp(' ')
0164     end
0165 
0166     def output_relationship(data, indent)
0167       # This implements output as per wrap-and-sort. That is:
0168       #   - sort all
0169       #     - substvars at the end
0170       #   - output >80 => line break each entry
0171       data.sort
0172       joined_alternatives = data.collect do |entry|
0173         entry.join(' | ')
0174       end
0175       output = joined_alternatives.join(', ')
0176       return output if output.size < (80 - indent)
0177       joined_alternatives.join(",\n#{Array.new(indent, ' ').join}")
0178     end
0179   end
0180 end