diff --git a/lib/inferno/apps/cli/evaluate.rb b/lib/inferno/apps/cli/evaluate.rb index 555832a1d..7d0ded97e 100644 --- a/lib/inferno/apps/cli/evaluate.rb +++ b/lib/inferno/apps/cli/evaluate.rb @@ -1,14 +1,30 @@ require_relative '../../../inferno/dsl/fhir_evaluation/evaluator' +require_relative '../../../inferno/dsl/fhir_evaluation/config' +require_relative '../../../inferno/dsl/ig' +require_relative '../../utils/ig_downloader' + +require 'tempfile' module Inferno module CLI - class Evaluate - def run(ig_path, data_path, _log_level) + class Evaluate < Thor::Group + include Thor::Actions + include Inferno::Utils::IgDownloader + + def evaluate(ig_path, data_path, _log_level) validate_args(ig_path, data_path) - # IG Import, rule execution, and result output below will be integrated at phase 2 and 3. + if File.file?(ig_path) + ig = Inferno::DSL::IG.from_file(ig_path) + else + Tempfile.create('package.tgz') do |temp_file| + load_ig(ig_path, nil, { force: true }, temp_file.path) + ig = Inferno::DSL::IG.from_file(temp_file.path) + end + end + + # Rule execution, and result output below will be integrated at phase 2 and 3. - # @ig = File.join(__dir__, 'ig', ig_path) # if data_path # DatasetLoader.from_path(File.join(__dir__, data_path)) # else diff --git a/lib/inferno/apps/cli/main.rb b/lib/inferno/apps/cli/main.rb index fdcdb4cdc..0e5e9b2ed 100644 --- a/lib/inferno/apps/cli/main.rb +++ b/lib/inferno/apps/cli/main.rb @@ -45,7 +45,7 @@ class Main < Thor type: :string, desc: 'Export evaluation result to outcome.json as an OperationOutcome' def evaluate(ig_path) - Evaluate.new.run(ig_path, options[:data_path], Logger::INFO) + Evaluate.new.evaluate(ig_path, options[:data_path], Logger::INFO) end desc 'console', 'Start an interactive console session with Inferno' diff --git a/lib/inferno/dsl/ig.rb b/lib/inferno/dsl/ig.rb new file mode 100644 index 000000000..cd767b467 --- /dev/null +++ b/lib/inferno/dsl/ig.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'fhir_models' +require 'rubygems/package' +require 'zlib' + +module Inferno + module DSL + # IG is a wrapper class around the relevant concepts inside an IG. + # Not everything within an IG is currently used by Inferno. + class IG + attr_accessor :profiles, :extensions, :value_sets, :search_params, :examples + + def initialize + @profiles = [] + @extensions = [] + @value_sets = [] + @examples = [] + @search_params = [] + end + + def self.from_file(ig_path) + raise "#{ig_path} is not an IG file" unless File.file?(ig_path) + + tar = Gem::Package::TarReader.new( + Zlib::GzipReader.open(ig_path) + ) + + # fhir_models by default logs the entire content of non-FHIR files + # which could be things like a package.json + original_logger = FHIR.logger + FHIR.logger = Logger.new('/dev/null') + + ig = IG.new + + tar.each do |entry| + next if skip_tar_entry? entry + + begin + resource = FHIR::Json.from_json(entry.read) + next if resource.nil? + + ig.add_resource(resource, entry) + rescue StandardError + next + end + end + + ig + ensure + FHIR.logger = original_logger if defined? original_logger + end + + # These files aren't FHIR resources + FILES_TO_SKIP = ['package.json', 'validation-summary.json'].freeze + + def self.skip_tar_entry?(entry) + return true if entry.directory? + + file_name = entry.full_name.split('/').last + + # TODO: consider making these regexes we can iterate over in a single loop + return true unless file_name.end_with? '.json' + return true unless entry.full_name.start_with? 'package/' + + return true if file_name.start_with? '.' # ignore hidden files + return true if file_name.end_with? '.openapi.json' + return true if FILES_TO_SKIP.include? file_name + + false + end + + def add_resource(resource, entry) + if resource.resourceType == 'StructureDefinition' + if resource.type == 'Extension' + extensions.push resource + else + profiles.push resource + end + elsif resource.resourceType == 'ValueSet' + value_sets.push resource + elsif resource.resourceType == 'SearchParameter' + search_params.push resource + elsif entry.full_name.start_with? 'package/example' + examples.push resource + end + end + end + end +end diff --git a/lib/inferno/utils/ig_downloader.rb b/lib/inferno/utils/ig_downloader.rb index 2f2ff9be3..f9acd0bff 100644 --- a/lib/inferno/utils/ig_downloader.rb +++ b/lib/inferno/utils/ig_downloader.rb @@ -14,7 +14,7 @@ def ig_file(suffix = nil) File.join(ig_path, suffix ? "package_#{suffix}.tgz" : 'package.tgz') end - def load_ig(ig_input, idx = nil, thor_config = { verbose: true }) + def load_ig(ig_input, idx = nil, thor_config = { verbose: true }, output_path = nil) case ig_input when FHIR_PACKAGE_NAME_REG_EX uri = ig_registry_url(ig_input) @@ -29,8 +29,9 @@ def load_ig(ig_input, idx = nil, thor_config = { verbose: true }) FAILED_TO_LOAD end + destination = output_path || ig_file(idx) # use Thor's get to support CLI options config - get(uri, ig_file(idx), thor_config) + get(uri, destination, thor_config) uri end