235 lines
No EOL
7.7 KiB
Ruby
235 lines
No EOL
7.7 KiB
Ruby
|
|
require_relative '../artefact.rb'
|
|
require_relative '../artefact_engine.rb'
|
|
|
|
module Comfpile
|
|
class ParserArtefact < Comfpile::Artefact
|
|
attr_reader :parsed_parameters
|
|
attr_reader :parsed_dependencies
|
|
|
|
def initialize(*args, search_regexes: nil, **opts)
|
|
super(*args, **opts)
|
|
|
|
@search_regexes = search_regexes
|
|
|
|
@parsed_parameters = {}
|
|
@parsed_dependencies = {}
|
|
|
|
parent_artefact :sourcefile, @target
|
|
require_artefact :config_file, File.dirname(@target)
|
|
|
|
add_step do
|
|
sourcefile_parse_step
|
|
sourcefile_dependency_step
|
|
end
|
|
|
|
end
|
|
|
|
# Attempts to find the set of values for a given
|
|
# parameter key.
|
|
#
|
|
# @param [String, Array<String>] keys Key, or array of keys, to look for
|
|
#
|
|
# @return [Array<String>] Array of values found for this key
|
|
#
|
|
def find_parsed_parameters(keys)
|
|
if keys.is_a? Array
|
|
output = []
|
|
keys.each do |key|
|
|
output += @parsed_parameters[key] || []
|
|
end
|
|
|
|
output
|
|
else
|
|
@parsed_parameters[keys] || []
|
|
end
|
|
end
|
|
|
|
def find_parsed_parameter(key)
|
|
find_parsed_parameters(key).last
|
|
end
|
|
|
|
# Try to find a relative or absolute source file
|
|
# artefact.
|
|
# Used to resolve the include, require and reference
|
|
# statements and find relative and/or absolute source
|
|
# files with matching name. Similar to how C++'s
|
|
# include statement it will check relative, then
|
|
# globally.
|
|
#
|
|
# @param [String, Array<String>] name Path of the sourcefile to look for
|
|
# @param [Boolean] optional If true, will also check for absolute
|
|
# source file, and will discard the file if it can't be found.
|
|
# Useful for e.g. C/C++ header files that may not be in the search
|
|
# path of comfpile.
|
|
#
|
|
# @return [String] Resolved path of the sourcefile. Relative
|
|
# if a relative file is found, else absolute.
|
|
#
|
|
def resolve_sourcefile(name, optional: false)
|
|
if(name.is_a? Array)
|
|
name.map do |n|
|
|
resolve_sourcefile(n, optional: optional)
|
|
end.compact
|
|
else
|
|
own_dir = File.dirname(@target)
|
|
relative_file = File.join(own_dir, name).gsub(/^\.\//, '')
|
|
|
|
if not craft_artefact(:sourcefile, relative_file).nil?
|
|
relative_file
|
|
elsif not craft_artefact(:sourcefile, name).nil?
|
|
name
|
|
elsif not optional
|
|
relative_file
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
|
|
def sourcefile_parse_step
|
|
log "Parsing file #{@target}..."
|
|
|
|
param_count = 0
|
|
|
|
File.readlines(@parent_artefact.file).each do |line|
|
|
@search_regexes.each do |r|
|
|
next if r[:regex].nil?
|
|
|
|
match = r[:regex].match line
|
|
next if match.nil?
|
|
match = match.named_captures
|
|
|
|
key = match['key'] || r[:key]
|
|
value = match['value'] || r[:value]
|
|
|
|
next if key.nil?
|
|
|
|
log "Found k/v pair: '#{key}' '#{value}'", :debug
|
|
param_count += 1
|
|
|
|
@parsed_parameters[key] ||= []
|
|
@parsed_parameters[key] << value
|
|
end
|
|
end
|
|
|
|
log "Parsing completed, found #{param_count} parameters!"
|
|
end
|
|
|
|
def sourcefile_dependency_step
|
|
['include', 'require', 'reference'].each do |key|
|
|
key_artefacts = resolve_sourcefile(find_parsed_parameters(key), optional: key == 'include')
|
|
|
|
@parsed_dependencies[key] = key_artefacts.map { |key| craft_artefact(:parsed, key) }
|
|
end
|
|
|
|
log "Generated dependencies. #{@parsed_dependencies.map { |k, v| "#{k}: #{v.size}" }.join(', ')}."
|
|
end
|
|
end
|
|
|
|
class DependencyAnalysisArtefact < Artefact
|
|
attr_reader :dependencies
|
|
attr_reader :mtime
|
|
|
|
def initialize(*args, traverse: nil, **opts)
|
|
super(*args, **opts)
|
|
|
|
parent_artefact :parsed, @target
|
|
|
|
@dependencies = []
|
|
|
|
@dependency_processed_artefacts = {}
|
|
@dependency_waiting_on = [ @parent_artefact ]
|
|
|
|
@traverse = traverse || ['include', 'require', 'reference']
|
|
|
|
add_step do
|
|
iterative_dependency_wait_step
|
|
end
|
|
|
|
add_step do
|
|
@mtime = @dependencies.map(&:mtime).max
|
|
end
|
|
end
|
|
|
|
def iterative_dependency_wait_step
|
|
dependency_unfurl = @dependency_waiting_on
|
|
@dependency_waiting_on = []
|
|
|
|
loop do
|
|
break if dependency_unfurl.empty?
|
|
|
|
artefact = dependency_unfurl.pop
|
|
|
|
next if @dependency_processed_artefacts[artefact.target]
|
|
|
|
@dependencies << artefact
|
|
@dependency_processed_artefacts[artefact.target] = true
|
|
|
|
artefact.parsed_dependencies.each do |key, dep_list|
|
|
next unless @traverse.nil? or @traverse.include? key
|
|
|
|
dep_list.each do |dep_artefact|
|
|
if wait_on dep_artefact
|
|
@dependency_waiting_on << dep_artefact
|
|
else
|
|
dependency_unfurl << dep_artefact
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
retry_step! unless @dependency_waiting_on.empty?
|
|
end
|
|
end
|
|
|
|
class ParserEngine < ArtefactEngine
|
|
#
|
|
# Initialize a new ParserEngine
|
|
#
|
|
# This engine has the task of generating single-file parser artefacts,
|
|
# which check for comfpile parameters that will be extracted from the sourcefile.
|
|
# Each file will recursively craft other files it requires and add them, either as
|
|
# fully included, required, or referenced file.
|
|
#
|
|
# @param [<Type>] core <description>
|
|
# @param [<Type>] **options <description>
|
|
#
|
|
def initialize(core, **options)
|
|
super(core, **options)
|
|
|
|
@input_file_regex = options[:file_regex]
|
|
@search_regexes = options[:search_regexes]
|
|
|
|
raise ArgumentError, "Missing input file regex!" unless @input_file_regex.is_a? Regexp
|
|
raise ArgumentError, "Missing regex parsing array!" unless @search_regexes.is_a? Array
|
|
end
|
|
|
|
def generate_parser_artefact(stage, target, context)
|
|
match = @input_file_regex.match target
|
|
return nil if match.nil?
|
|
|
|
ParserArtefact.new(@core, self, stage, target, context,
|
|
search_regexes: @search_regexes)
|
|
end
|
|
|
|
def generate_dependency_analysis_artefact(stage, target, context, **opts)
|
|
match = @input_file_regex.match target
|
|
return nil if match.nil?
|
|
|
|
DependencyAnalysisArtefact.new(@core, self, stage, target, context, **opts)
|
|
end
|
|
|
|
def craft(stage, target, context)
|
|
case stage
|
|
when :parsed
|
|
generate_parser_artefact(stage, target, context)
|
|
when /^dependency_analysis(?:_(.+))?$/
|
|
traverse = $1&.split('_')
|
|
generate_dependency_analysis_artefact(stage, target, context, traverse: traverse)
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
end |