lib/wow/package/specification.rb
require 'toml'
require 'wow/package/file_pattern'
require 'wow/package/specification_lock'
# Package Specification
# This class is the mapping of the user specification defined in the oml file
class Wow::Package::Specification
include ActiveModel::Validations
include Wow::Package::SpecAttributes
# User config
attr_reader :files_included
attr_reader :files_excluded
# Internal Config
attr_accessor :platform_configs
validates_presence_of :name, :version
validates_format_of :name,
with: /\A[a-z0-9_-]+\z/,
message: 'Error in config file. Should only contain lowercase, numbers and _-'
def self.filename
'wow.toml'
end
# Will load the config from the current working directory
# @return loaded config
def self.load
from_toml(Wow::Package::Specification.filename)
end
# Will load the config from the current working directory and check it's valid
# @return loaded config
# @throw Wow::Error if the config is invalid @see Wow::Specification.validate!
def self.load_valid!
config = load
config.validate!
config
end
def self.from_toml(file)
hash = TOML.load_file(file).deep_symbolize_keys
spec = Wow::Package::Specification.new(hash)
spec.files_included.unshift Wow::Package::FilePattern.new(file)
spec
end
# Initialize a new specification
def initialize(hash = {})
replace_attributes(hash)
self.files_included = hash[:files]
self.files_excluded = hash[:files_excluded]
@platform_configs = {}
load_platforms(hash[:platform])
end
# Load the platform_name specific spec
def load_platforms(platform_hash)
return if platform_hash.nil?
platform_hash.each do |platform_name, data|
load_platform(platform_name, data)
end
end
# Load the platform_name from the hash
# Format can be
# * platform_name.[Spec]
# * platform_name.arch.[Spec]
# @param platform_name [String] name of the platform_name
# @param data [Hash] Platform specific specs.
def load_platform(platform_name, data)
exclude_arch = []
# Check if arch specific specs are defined
data.each do |arch_name, content|
next unless Wow::Package::Target.architectures.exist?(arch_name)
platform = Wow::Package::Target.new(platform_name, arch_name)
exclude_arch << arch_name
platform_config = Wow::Package::Specification.new(content)
@platform_configs[platform] = platform_config
end
return if exclude_arch.size == data.size
platform = Wow::Package::Target.new(platform_name)
platform_config = Wow::Package::Specification.new(data.except(exclude_arch))
@platform_configs[platform] = platform_config
end
# Add a new file to the specification
# @param files [String|Array] File or list of files
def file(files)
@files_included += [*files].map { |x| Wow::Package::FilePattern.new(x) }
end
# Exclude a file from the specification
# @param files [String|Array] File or list of files
def exclude(files)
@files_excluded += [*files].map { |x| Wow::Package::FilePattern.new(x) }
end
def platform(name, &block)
platform_configs << {plaform: Wow::Package::Target.new(name), block: block}
end
# Set the list of files included
# @param files [Array] files
def files_included=(files)
files ||= []
@files_included = files.map do |x|
if x.is_a? Wow::Package::FilePattern
x
else
Wow::Package::FilePattern.new(x)
end
end
end
# Set the list of files excluded
# @param files [Array] files
def files_excluded=(files)
files ||= []
@files_excluded = files.map do |x|
if x.is_a? Wow::Package::FilePattern
x
else
Wow::Package::FilePattern.new(x)
end
end
end
# List all the defined platforms
# @return [Array<Wow::Package::Target>]
def platforms
@platform_configs.keys
end
def list_files_matching_patterns(file_patterns = [])
patterns = [*file_patterns]
results = {}
patterns.each do |file_pattern|
results.merge! file_pattern.file_map
end
results
end
# @return all files matching the pattern given in the files
def files
included_files = list_files_matching_patterns(@files_included)
excluded_files = list_files_matching_patterns(@files_excluded)
included_files.except(excluded_files.keys)
end
# Return the platform_name specific config
# i.e Flatten the platform_name specific Specs
# @return [Wow::Package::Config]
def get_platform_config(platform, architecture = :any)
config = clone
config.target = Wow::Package::Target.new(platform, architecture)
@platform_configs.each do |target, specification|
config.merge!(specification) if config.target.is? target
end
config
end
def lock(platform, architecture = nil)
spec_lock = Wow::Package::SpecificationLock.new(platform, architecture)
spec_lock.insert_specification(self)
@platform_configs.each do |target, specification|
if spec_lock.target.is? target
spec_lock.insert_specification(specification)
end
end
spec_lock
end
def merge!(other)
@files_included += other.files_included
@files_excluded += other.files_excluded
@tags += other.tags
end
def merge(other)
clone.merge!(other)
end
# Raise am error if the config is invalid
# @return [Boolean] true if succeed and raise WowError if not
def validate!
fail Wow::Error, errors.full_messages unless valid?
true
end
# Build an archive from this config
# @param destination Destination folder of the archive file
# @return archive path with filename
def create_archive(platform, architecture = nil, destination: nil)
validate!
spec_lock = lock(platform, architecture)
spec_lock.save
destination ||= Dir.pwd
path = File.join(destination, spec_lock.name_tuple.archive_filename)
Wow::Archive.write path do |archive|
archive.add_file spec_lock.filename
archive.add_files files
end
path
end
# Equivalent to creating the archive then installing the archive to the given destination
# To install a program directly from the source(not an archive)
# @param destination [String] folder where to install files
def install_to(platform, architecture = nil, destination: nil)
destination ||= File.join(Wow::Config.package_install_root, package_folder)
spec_lock = lock(platform, architecture)
spec_lock.save
files.merge(spec_lock.filename => spec_lock.filename).each do |source, file_destination|
output = File.join(destination, file_destination)
FileUtils.mkdir_p(output)
FileUtils.cp(source, output)
end
end
end