#!/usr/bin/env ruby require "concurrent" require "stepmod/utils/stepmod_file_annotator" require "fileutils" require "bundler/setup" require "optparse" def log(message, indent = 0) indent_spaces = " " * (indent * 2) puts "[stepmod-utils] #{indent_spaces}#{message}" end options = {} OptionParser.new do |opts| opts.banner = "Usage: #{$0} [options]" opts.on( "--stepmod-dir STEPMOD_DIR", String, "Path to STEPmod directory", ) do |path| options[:stepmod_dir] = path end opts.on( "--schemas SRL_SCHEMAS_DIR", String, "Path to output SRL schemas", ) do |path| options[:srl_output_schemas_dir] = path end opts.on( "--documents SRL_DOCS_DIR", String, "Path to SRL documents (generated by stepmod2mn)", ) do |path| options[:srl_output_docs_dir] = path end opts.on( "--eqn-log LOG_FILE_DIR", String, "Path to the directory for log file for equations", ) do |path| options[:eqn_log_dir] = path end opts.on_tail("-h", "--help", "Show this message") do puts opts exit end end.parse! stepmod_dir = options[:stepmod_dir] raise StandardError.new("--stepmod-dir not set") unless stepmod_dir stepmod_path = Pathname.new(stepmod_dir) unless (stepmod_path.join("data").exist? && stepmod_path.join("data").directory?) raise StandardError.new("--stepmod-dir should be the root STEPmod directory and contains data/") end log "STEPmod data path: `#{stepmod_dir}`" srl_output_schemas_dir = options[:srl_output_schemas_dir] raise StandardError.new("--schemas directory for SRL schemas not set") unless srl_output_schemas_dir log "SRL schemas output path: `#{srl_output_schemas_dir}`" srl_output_docs_dir = options[:srl_output_docs_dir] raise StandardError.new("--documents directory for SRL documents (generated by stepmod2mn) not set") unless srl_output_docs_dir log "SRL documents path: `#{srl_output_docs_dir}`" unless File.exist?(srl_output_docs_dir) && File.directory?(srl_output_docs_dir) raise StandardError.new("--documents directory for SRL documents (generated by stepmod2mn) does not exist yet, please run stepmod2mn") end eqn_log_dir = File.expand_path(options[:eqn_log_dir] || ".") Stepmod::Utils.eqn_log_dir = eqn_log_dir def all_express_files(stepmod_dir) index_file = File.read(File.join(stepmod_dir, "repository_index.xml")) index = Nokogiri::XML(index_file).root files = [] index.xpath("modules/module").each do |item| files << "#{stepmod_dir}/data/modules/#{item['name']}/arm.exp" files << "#{stepmod_dir}/data/modules/#{item['name']}/mim.exp" end index.xpath("resources/resource").each do |item| next if item["name"] == "iso13584_expressions_schema" files << "#{stepmod_dir}/data/resources/#{item['name']}/#{item['name']}.exp" end index.xpath("business_object_models/business_object_model").each do |item| files << "#{stepmod_dir}/data/business_object_models/#{item['name']}/bom.exp" files << "#{stepmod_dir}/data/business_object_models/#{item['name']}/DomainModel.exp" end files.filter { |file| File.exist?(file) } end # On MacOS File.exist? was behaving case sensitive while on # Github Actions it was behaving as case in-sensitive, e.g # File.exist?(`../resource_docs/geometric_and_topological_representation/RationalLRsurf.gif`) # was returning true on MacOS and false on Github Action runner. Because # the original filepath was `geometric_and_topological_representation/RationalLRSurf.gif` (notice the capital `S` for Surf.gif) # So this method is to find the actual path of the file based on # downcased name of the file, # # Example: # # Given `../resource_docs/geometric_and_topological_representation/RationalLRsurf.gif` # return `../resource_docs/geometric_and_topological_representation/RationalLRSurf.gif` # It will return nil if the file does not exist def file_system_path(filepath, filename_cache) dir = File.dirname(filepath) return filename_cache[dir][filepath.downcase] if filename_cache[dir] files = Dir.glob(File.join(dir, "*")).map { |f| [f.downcase, f] }.to_h filename_cache[dir] = files filename_cache[dir][filepath.downcase] end MAX_THREADS = 1 #[2, Concurrent.processor_count].max * 2 MAX_QUEUE_SIZE = MAX_THREADS * 4 # https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/thread_pools.md # pool = Concurrent::ThreadPoolExecutor.new( # min_threads: MAX_THREADS, # max_threads: MAX_THREADS, # max_queue: MAX_QUEUE_SIZE, # fallback_policy: :caller_runs, # ) files = all_express_files(stepmod_dir) filename_cache = {} FileUtils.mkdir_p(srl_output_schemas_dir) srl_output_schemas_path = Pathname.new(srl_output_schemas_dir) srl_output_docs_path = Pathname.new(srl_output_docs_dir) files.each do |file| puts "#{Thread.current.object_id}: `#{file}` processing..." result = Stepmod::Utils::StepmodFileAnnotator.new( express_file: file, stepmod_dir: stepmod_dir, ).call destination_rel_path = Pathname.new(file).dirname.relative_path_from(stepmod_path.join("data")) directory_type = destination_rel_path.each_filename.to_a.first destination_path = srl_output_schemas_path.join(destination_rel_path) FileUtils.mkdir_p(destination_path) annotated_file_path = destination_path.join(File.basename(file)) File.open(annotated_file_path, "w") do |annotated_file| annotated_file.puts(result[:annotated_text]) end puts "#{Thread.current.object_id}: Written to: `#{annotated_file_path}`" result[:images_references].each do |source, destination| source_path = stepmod_path.join("data", source) system_source_path = file_system_path(source_path.to_s, filename_cache) image_dest_path = destination_path.join(destination) if image_dest_path.exist? puts "#{Thread.current.object_id}: Copy image SKIPPED for `#{image_dest_path}` as destination exists." next end unless system_source_path source_file = Pathname.new(source).basename.to_s puts "#{Thread.current.object_id}: Copy image NOT FOUND at schema path: `#{source_file}`, trying glob..." source_path = (srl_output_schemas_path.glob("**/#{source_file}") + srl_output_docs_path.glob("**/#{source_file}")).first if source_path puts "EXISTS" if source_path.exist? system_source_path = file_system_path(source_path.to_s, filename_cache) puts "#{Thread.current.object_id}: Copy image FOUND at different path: `#{system_source_path}`" end end unless system_source_path puts "#{Thread.current.object_id}: Copy image MISSING from `#{source_path}` to `#{image_dest_path}`" next end FileUtils.cp(system_source_path, image_dest_path) puts "#{Thread.current.object_id}: Copy image SUCCESS: `#{system_source_path}` to `#{image_dest_path}`" end puts "#{Thread.current.object_id}: Done processing `#{File.basename(file)}` => `#{annotated_file_path}`" end