require_relative "version" require "fiber" require_relative "code_recognizer" require_relative "objc_2_swift" require_relative "objc_2_swift_configuration" require_relative "processing_element" require "swift_generator/code_generation/swift_class_generation" require "swift_generator/code_generation/swift_file_template" require "swift_generator/code_generation/swift_class_generation" module Objc2swiftAssistant class FileSet < FailableProcessingElement attr_accessor :root attr_accessor :root_dir_node attr_accessor :directory_nodes_by_path attr_accessor :configuration def initialize( root, configuration ) super() @configuration = configuration @root = Pathname.new( root ) @directory_nodes_by_path = {} end def directory_node_for_path( path ) node = @directory_nodes_by_path[ path.to_s ] if node.nil? node = make_directory_node( path ) @directory_nodes_by_path[ path.to_s ] = node end return node end def make_directory_node( path ) node = DirectoryNode.new( path, self ) node end def dump( indent, tab, errors_only ) if errors_only puts( "\nWARNING: You specified '--structure errors'") puts( " This option (Logging only only the file structure that caused errors) is not yet supported. The full source file structure will be logged.\n\n" ) end @configuration.log_verbose( indent + "File Set") @root_dir_node.dump( indent+tab, tab, errors_only ) end # Create nodes for all directories and files of interest # Use find() to limit stack depth def scan() @root.find() do |path| if path.directory? process_directory( path ) else process_file( path ) end end @configuration.log_verbose( "Processed #{@directory_nodes_by_path.size} directories.") collect_active_file_nodes() end end class ScannedFileSet < FileSet attr_accessor :active_file_nodes def initialize( root, configuration ) super( root, configuration ) end def process_directory(path) directory_node = directory_node_for_path( path ) if directory_node.relative_path.to_s == '.' @root_dir_node = directory_node end @configuration.log_verbose( "Processing Directory: #{path.to_s}") end def process_file(path) @configuration.log_verbose( "Processing File: #{path.to_s}") dir_node = directory_node_for_path( path.parent ) dir_node.process_file( path ) end def collect_active_file_nodes @active_file_nodes = [] @directory_nodes_by_path.each do |_, directory_node | @configuration.log_verbose( "Collecting file nodes for directory:#{directory_node.full_path}") directory_node.file_nodes_by_name.each do |_, file_node | file_node.prepare_for_use() if file_node.should_be_used @active_file_nodes << file_node end end end end end class DirectoryNode attr_accessor :file_set attr_accessor :file_nodes_by_name attr_accessor :full_path attr_accessor :relative_path attr_accessor :child_directories attr_accessor :parent_node attr_accessor :pruned def initialize(path, file_set) @full_path = path @file_set = file_set if file_set.root == @full_path @parent_node = nil else @parent_node = file_set.directory_node_for_path( path.parent ) end @relative_path = @full_path.relative_path_from( file_set.root ) @child_directories = {} @file_nodes = {} @pruned = false @file_nodes_by_name = {} #todo: Apply settings unless @parent_node.nil? @parent_node.add_child_directory( self ) end end # Add a child directory. Mark the child as pruned if this node is pruned. Note that this keeps # pruned nodes around which is desirable for reporting the effects of code generation def add_child_directory( child_node ) @child_directories[ child_node.relative_path.basename().to_s ] = child_node child_node.pruned = @pruned end def dump( indent, tab, errors_only ) puts( indent + "Path: #{@relative_path.to_s}") puts( indent + "Files:") unless @file_nodes_by_name.length == 0 @file_nodes_by_name.each_value { |file| file.dump( indent+tab, tab, errors_only ) } puts( "\n" + indent + "Child Directories:") unless @child_directories.length == 0 @child_directories.each_value { |dir| dir.dump( indent+tab, tab, errors_only ) } end end class ObjCDirectoryNode < DirectoryNode attr_accessor :associated_generated_directory def initialize(path, file_set) super(path, file_set) end def process_file( path ) basename = path.basename without_extension = path.basename(".h") || path.basename(".hh") if without_extension != basename file_node = file_node_for_file( without_extension.to_s ) file_node.header_file_path = path return end without_extension = path.basename(".m") || path.basename(".mm") if without_extension != basename file_node = file_node_for_file( without_extension.to_s ) file_node.implementation_file_path = path return end @file_set.configuration.log_verbose( "File not captured:#{path.to_s}") end def file_node_for_file( without_extension_string ) file_node = @file_nodes_by_name[ without_extension_string ] if file_node.nil? file_node = ObjCFileNode.new( self, without_extension_string ) @file_nodes_by_name[ without_extension_string ] = file_node end return file_node end def create_associated_generated_nodes( generated_file_set ) # puts( "generaating file node: #{@relative_path}") #TODO: Break this out into Swift generation-specific ruby file return if @pruned # #TODO: allow mapping to new directory scruture for this file generated_relative_path = generated_file_set.root + @relative_path @associated_generated_directory = generated_file_set.directory_node_for_path( generated_relative_path ) @file_nodes_by_name.each_value do |file_node| # puts( "generaating file node: #{file_node.}") file_node.create_generated_file_nodes( generated_file_set, @associated_generated_directory ) end end end class SourceFileNode attr_accessor :directory_node attr_accessor :should_be_used attr_accessor :file_set def initialize( directory_node ) @directory_node = directory_node @file_set = directory_node.file_set @should_be_used = true end end class ObjCFileNode < SourceFileNode attr_accessor :name_root attr_accessor :header_file_path attr_accessor :implementation_file_path attr_accessor :processed_header_file attr_accessor :processed_implementation_file attr_accessor :recognized_code attr_accessor :objc_2_swift_converter def initialize(directory_node, name_root ) super( directory_node ) @name_root = name_root end def prepare_for_use # puts( "prepare_for_use() header: #{@header_file_path}, implementation: #{@implementation_file_path}") @recognized_code = [] @processed_header_file = ProcessedSourceFile.new( @header_file_path, :header, @file_set.configuration ) unless @header_file_path.nil? || @file_set.omit_file( relative_header_path ) @processed_implementation_file = ProcessedSourceFile.new( @implementation_file_path, :implementation, @file_set.configuration ) unless @implementation_file_path.nil? || @file_set.omit_file( relative_impl_path ) end def recognize_code_fragments( recognizers ) @file_set.configuration.log_verbose( "Recognizing code fragments in #{@header_file_path}, #{@implementation_file_path}") @processed_header_file.recognize_code_fragments( recognizers ) unless @processed_header_file.nil? @processed_implementation_file.recognize_code_fragments( recognizers ) unless @processed_implementation_file.nil? end def prepare_conversion( configuration ) @objc_2_swift_converter = ObjC2SwiftFileConverter.new( self, configuration ) @objc_2_swift_converter.prepare end def root_matches root_matches = [] root_matches.concat( @processed_header_file.root_matches ) unless @processed_header_file.nil? root_matches.concat( @processed_implementation_file.root_matches ) unless @processed_implementation_file.nil? return root_matches end def create_generated_file_nodes( generated_file_set, generated_directory ) swift_file_name = @name_root + '.swift' generated_file_node = generated_directory.file_nodes_by_name[ swift_file_name ] if generated_file_node.nil? generated_file_node = GeneratedSwiftFileNode.new( generated_directory, swift_file_name ) generated_directory.file_nodes_by_name[ swift_file_name ] = generated_file_node end @objc_2_swift_converter.swift_file_node = generated_file_node end def dump( indent, tab, errors_only ) # puts( indent + "NameRoot: #{@name_root}") puts( indent + "#{@name_root} :") unless @processed_header_file.nil? puts( indent+tab + "Header:") @processed_header_file.dump( indent+tab+tab, tab, errors_only ) end unless @processed_implementation_file.nil? puts( indent+tab + "Implementation:") @processed_implementation_file.dump( indent+tab+tab, tab, errors_only ) end unless @objc_2_swift_converter.nil? puts( indent+tab + "Swift File Generation:") @objc_2_swift_converter.dump( indent+tab+tab, tab, errors_only ) end end def relative_header_path if @header_file_path.nil? return nil else return @directory_node.relative_path.join( @header_file_path.basename ) end end def relative_impl_path if @implementation_file_path.nil? return nil else return @directory_node.relative_path.join( @implementation_file_path.basename ) end end # Utility def cannonical_source_file_path source_path = relative_impl_path source_path ||= relative_header_path source_path end end class ProcessedSourceFile attr_accessor :file_path attr_accessor :file_type attr_accessor :root_matches attr_accessor :inner_matches attr_accessor :configuration def initialize( file_path, file_type, configuration ) @file_path = file_path @file_type = file_type @configuration = configuration @root_matches = [] @inner_matches = [] end def recognize_code_fragments( recognizers ) file_lines = Objc2swiftAssistant::de_comment_lines( @file_path.readlines ) @configuration.log_verbose( "\nRecognizing code fragments in #{@file_path.to_s}" ) recognizers.each do |recognizer| if recognizer.should_scan_file( @file_type ) matches = recognizer.matches( file_lines ) matches.each do |match| unless match_already_found( match ) if match.is_root_entity @root_matches << match else @inner_matches << match unless match_already_found( match ) end end end end end # Sort both sets of matches by the starting line number @root_matches.sort_by!{ |m| m.starting_line_number } @inner_matches.sort_by!{ |m| m.starting_line_number } # Delimit the root matches in the file previous_match = nil @root_matches.each do |match| previous_match.ending_line_number = match.starting_line_number - 1 unless previous_match.nil? previous_match = match end previous_match.ending_line_number = file_lines.count - 1 unless previous_match.nil? # Associate the inner_matches with the root matches that they belong to root_match_fiber = Fiber.new do if @root_matches @root_matches.each do |match| Fiber.yield match end end Fiber.yield nil end # puts( root_match_fiber.resume.region_type_name ) if root_match_fiber.alive? # puts( root_match_fiber.resume.region_type_name ) if root_match_fiber.alive? # puts( root_match_fiber.resume.region_type_name ) if root_match_fiber.alive? # Add inner matches to the containing root match, if possible if @root_matches.length > 0 current_root_match = nil @inner_matches.each do |inner_match| while( current_root_match.nil? || ! current_root_match.contains_line( inner_match.starting_line_number ) ) if root_match_fiber.alive? current_root_match = root_match_fiber.resume # puts(current_root_match) if current_root_match.nil? configuration.log_verbose( "Uncontained inner match: #{inner_match}") end else configuration.log_verbose( "Uncontained inner match: #{inner_match}") current_root_match = nil break end end current_root_match.add_sub_region( inner_match ) unless current_root_match.nil? end end @root_matches.each do |root_match| root_match.complete( file_lines ) configuration.log_verbose( " added match: #{root_match.description} in file: #{@file_path.to_s}") end end def match_already_found( new_match ) return match_already_exists( new_match, @root_matches ) || match_already_exists( new_match, @inner_matches ) end def match_already_exists( new_match, existing_matches ) existing_matches.each do | existing_match | if new_match.starting_line_number == existing_match.starting_line_number @configuration.log_verbose( "match : #{new_match.brief_description} found at existing match: #{existing_match.brief_description}") return true end end return false end def organize_matches @raw_matches.each do |match| end end def dump( indent, tab, errors_only ) puts( indent + "Path: #{@file_path.to_s}") puts( indent + "Type: #{@file_type.to_s}") puts( indent + 'Matches:') @root_matches.each do |region| region.dump( indent + tab, tab, errors_only ) end end end class ObjCFileSet < ScannedFileSet attr_accessor :code_recognizers attr_accessor :header_file_extensions attr_accessor :implmentation_file_extensions attr_accessor :generated_swift_file_set def initialize( root, code_recognizers, configuration:nil ) super( root, configuration ) @code_recognizers = code_recognizers @header_file_extensions=[ '.h', '.hh' ] @implmentation_file_extensions=[ '.m', '.mm' ] end #Factory Methods def make_directory_node( path ) ObjCDirectoryNode.new( path, self ) end def recognize_code_fragments() @active_file_nodes.each do |file_node| file_node.prepare_for_use file_node.recognize_code_fragments( @code_recognizers ) end end def prepare_conversion() # TODO: Error handling # puts( "1: #{@configuration.config_value( "Test", "company_name")}" ) # puts( "2: #{@configuration.config_value( "SomePathTest", "company_name")}" ) # puts( "3: #{@configuration.config_value( ".", "company_name")}" ) # puts( "4: #{@configuration.config_value( "Runtime/Channel", "company_name")}" ) @active_file_nodes.each do |file_node| file_node.prepare_conversion( @configuration ) end end def generate_swift_file_set(root, dry_run) @generated_swift_file_set = GeneratedSwiftFileSet.new( root, @configuration ) # Map the original source files to the new generated source files @directory_nodes_by_path.each_value do |node | node.create_associated_generated_nodes( @generated_swift_file_set ) end # Create the Swift code generator generator_definitions = SwiftGenerator::SwiftDefinitionSet.new( generated_root:@generated_swift_file_set.root.to_s ) generator_definitions.make_unknown_types = true @active_file_nodes.each do |file_node| file_node.objc_2_swift_converter.generate( generator_definitions, @configuration, dry_run ) end generator_definitions.run_generation_sequence() SwiftGenerator::write_files_for_definition_set( generator_definitions ) end def omit_file( path ) return @configuration.omit_file( path ) end end # # Generated Swift File Sets # # Swift file sets simplify mapping to a new directory structure and the application on customization that # will be associated with nodes in the original directory structure class GeneratedSwiftFileSet < FileSet def initialize( root, configuration ) super( root, configuration ) end end class GeneratedSwiftFileNode < SourceFileNode attr_accessor :full_path attr_accessor :relative_path def initialize( directory_node, file_name ) super( directory_node ) @full_path = directory_node.full_path + file_name @relative_path = directory_node.relative_path + file_name end end # class SwiftDirectoryNode < DirectoryNode # # def initialize(objc_node, parent_node, file_set) # #todo: Apply settings # # @parent_node = parent_node # @relative_path = objc_node.relative_path # @full_path = file_set.root + @relative_path # # @child_directories = {} # @file_nodes = {} # # unless @parent_node.nil? # @parent_node.add_child_directory( self ) # end # # @pruned = false # end # # def generate_files() # @full_path.mkpath # @file_nodes.each { |node| node.generate_files } # @child_directories.each { |node| node.generate_files } # end # # end # class SwiftFileSet < FileSet # # def initialize(root) # super(root) # end # # def make_directory_node( path ) # SwiftDirectoryNode.new( path, self ) # end # end end