Class: FPM::Package::Python::PythonMetadata

Inherits:
Object
  • Object
show all
Defined in:
lib/fpm/package/python.rb

Defined Under Namespace

Classes: MissingField, UnexpectedContent

Constant Summary collapse

MULTIPLE_USE =

According to packaging.python.org/en/latest/specifications/core-metadata/ > Core Metadata v2.4 - August 2024

%w(Dynamic Platform Supported-Platform License-File Classifier Requires-Dist Requires-External Project-URL Provides-Extra Provides-Dist Obsoletes-Dist)
FIELD_MAP =
{
  :@name => "Name",
  :@version => "Version",
  :@summary => "Summary",
  :@description => "Description",
  :@keywords => "Keywords",
  :@maintainer => "Author-email",

  # Note: License can also come from the deprecated "License" field
  # This is processed later in this method.
  :@license => "License-Expression",

  :@requires => "Requires-Dist",
}
REQUIRED_FIELDS =
[ "Metadata-Version", "Name", "Version" ]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(headers, body = nil) ⇒ PythonMetadata

headers - a Hash containing field-value pairs from headers as read from a python METADATA file. body - optional, a string containing the body text of a METADATA file



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/fpm/package/python.rb', line 200

def initialize(headers, body=nil)
  REQUIRED_FIELDS.each do |field|
    if !headers.include?(field)
      raise MissingField, "Missing required Python metadata field, '#{field}'. This might be a bug in the package or in fpm."
    end
  end

  FIELD_MAP.each do |attr, field|
    if headers.include?(field)
      instance_variable_set(attr, headers.fetch(field))
    end
  end

  # Do any extra processing on fields to turn them into their expected content.
  process_description(headers, body)
  process_license(headers)
  process_homepage(headers)
  process_maintainer(headers)
end

Instance Attribute Details

#descriptionObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def description
  @description
end

#homepageObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def homepage
  @homepage
end

#keywordsObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def keywords
  @keywords
end

#licenseObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def license
  @license
end

#maintainerObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def maintainer
  @maintainer
end

#nameObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def name
  @name
end

#requiresObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def requires
  @requires
end

#summaryObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def summary
  @summary
end

#versionObject (readonly)

Only focusing on terms fpm may care about



179
180
181
# File 'lib/fpm/package/python.rb', line 179

def version
  @version
end

Class Method Details

.from(input) ⇒ Object

self.parse



174
175
176
# File 'lib/fpm/package/python.rb', line 174

def self.from(input)
  return PythonMetadata.new(*parse(input))
end

.parse(input) ⇒ Object

METADATA files are described in Python Packaging “Core Metadata”[1] and appear to have roughly RFC822 syntax.

1

packaging.python.org/en/latest/specifications/core-metadata/#core-metadata



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/fpm/package/python.rb', line 113

def self.parse(input)
  s = StringScanner.new(input)
  headers = {}

  # Default "Multiple use" fields to empty array instead of nil.
  MULTIPLE_USE.each do |field|
    headers[field] = []
  end

  while !s.eos? and !s.scan("\n") do 
    # Field is non-space up, but excluding the colon
    field = s.scan(/[^\s:]+/)

    # Skip colon and following whitespace
    s.scan(/:\s*/)

    # Value is text until newline, and any following lines if they have leading spaces.
    value = s.scan(/[^\n]+(?:\Z|\n(?:[ \t][^\n]+\n)*)/)
    if value.nil?
      raise "Failed parsing Python package metadata value at field #{field}, char offset #{s.pos}"
    end
    value = value.chomp

    if MULTIPLE_USE.include?(field)
      raise "Header field should be an array. This is a bug in fpm." if !headers[field].is_a?(Array)
      headers[field] << value
    else
      headers[field] = value
    end
  end # while reading headers

  # If there's more content beyond the last header, then it's a content body.
  # In Python Metadata >= 2.1, the descriptino can be written in the body.
  if !s.eos? 
    if headers["Metadata-Version"].to_f >= 2.1
      # Per Python core-metadata spec:
      # > Changed in version 2.1: This field may be specified in the message body instead.
      #return PythonMetadata.new(headers, s.string[s.pos ...])
      return headers, s.string[s.pos ... ]
    elsif headers["Metadata-Version"].to_f >= 2.0
      # dnspython v1.15.0 has a description body and Metadata-Version 2.0 
      # this seems out of spec, but let's accept it anyway.
      return headers, s.string[s.pos ... ]
    else
      raise "After reading METADATA headers, extra data is in the file but was not expected. This may be a bug in fpm."
    end
  end

  #return PythonMetadata.new(headers)
  return headers, nil # nil means no body in this metadata
rescue => e
  puts "String scan failed: #{e}"
  puts "Position: #{s.pointer}"
  puts "---"
  puts input
  puts "==="
  puts input[s.pointer...]
  puts "---"
  raise e
end