# # File:: data_string_analyser.rb # Description:: # # Author:: David Muir # Date:: 11 November 2008 # #---------------------------------------------------------------------------- # Uses #---------------------------------------------------------------------------- require 'pipeline/os/path' require 'pipeline/util/string' #---------------------------------------------------------------------------- # Implementation #---------------------------------------------------------------------------- module Pipeline module ProjectUtil # # == Description # The main code dependency analyser entry-point. This function takes a # list of files to analyse using the registered StringAnalyser classes # and invokes the user-block for each dependency it finds. # # The user-block should take two arguments, the first being the absolute # path of the file being analysed, the second being the string recognised # by the analyser as the dependency. # # The dependencies are also returned in a Hash with filenames as the # key and an Array of dependency String objects. # # === Example Usage # # filelist = OS::find_files_recurse( root_path, '*.*' ) # ProjectUtil::code_dependency_analyser( filelist ) do |file, dependency| # ## User-code # end # def ProjectUtil::code_dependency_analyser( files, analyser, &block ) throw ArgumentError.new( "Invalid StringAnalyser specified (#{analyser.class})." ) \ unless ( analyser.is_a?( StringAnalyser ) ) dependencies = {} files.each do |filename| # Skip file if our analyser doesn't know about it. extension = OS::Path::get_extension( filename ) next unless ( analyser.extensions.include?( extension ) ) # Read all file data as text. data = [] File.open( filename, 'r' ) do |fp| data = fp.readlines( ) end # Loop through file content line by line. data.each do |line| # Check each pattern. analyser.patterns.each_pair do |regexp, index| md = line.match( regexp ) next if ( md.nil? ) # We have a match so we invoke the user block. yield( filename, md[index] ) if ( block_given? ) # Complete Hash being returned. dependencies[filename] = [] unless ( dependencies.has_key?( filename ) ) dependencies[filename] << md[index] end end end dependencies end # # == Description # String analyser base class. The constructor takes a file extension # argument and a Hash object mapping regular expressions to integer # indices within the matchdata Array for the (typically filename) # dependency. # # In general, the index Hash value will be 1 unless you are reusing the # Regexp object from another script. # class StringAnalyser attr_reader :extensions attr_reader :patterns def initialize( extensions, patterns ) throw ArgumentError.new( "Invalid Array of extensions (#{extensions.class})." ) \ unless ( extensions.is_a?( Array ) ) throw ArgumentError.new( "Invalid Hash of patterns (#{patterns.class})." ) \ unless ( patterns.is_a?( Hash ) ) @extensions = extensions @patterns = patterns end def to_s() "#{self.class}: #{@extensions.join( ' ' )} #{@patterns.join( '; ' )}" end def StringAnalyser::registered_analysers( ) @@registered_analysers end def StringAnalyser::inherited( subclass ) @@registered_analysers = [] if ( @registered_analysers.nil? ) @@registered_analysers << subclass end end # # == Description # Ruby file string dependency analyser. # class RubyFileDependencyAnalyser < StringAnalyser def initialize( ) super( EXTENSIONS, PATTERNS ) end #---------------------------------------------------------------------- # Private Data #---------------------------------------------------------------------- private EXTENSIONS = [ 'rb', 'rbw' ] # Define some useful Regexp objects to fill in our patterns Hash. REGEXP_REQUIRE_DQ = /^require \"(.*)\"/i REGEXP_REQUIRE_SQ = /^require \'(.*)\'/i REGEXP_GEM_DQ = /^gem \"(.*)\"/i REGEXP_GEM_SQ = /^gem \'(.*)\'/i # Define our patterns hash, using our regexp constants above. PATTERNS = { REGEXP_REQUIRE_DQ => 1, REGEXP_REQUIRE_SQ => 1, REGEXP_GEM_DQ => 1, REGEXP_GEM_SQ => 1, } end # # == Description # MaxScript file string dependency analyser. # class MaxScriptFileDependencyAnalyser < StringAnalyser def initialize( ) super( EXTENSIONS, PATTERNS ) end #---------------------------------------------------------------------- # Private Data #---------------------------------------------------------------------- private EXTENSIONS = [ 'ms', 'mcr' ] # Define some useful Regexp objects to fill in our patterns Hash. REGEXP_FILEIN = /^filein \"(.*)\"/i REGEXP_INCLUDE = /^include \"(.*)\"/i # Define our patterns hash, using our regexp constants above. PATTERNS = { REGEXP_FILEIN => 1, REGEXP_INCLUDE => 1, } end end # ProjectUtil module end # Pipeline module if ( __FILE__ == $0 ) then begin require 'pipeline/os/file' include Pipeline files = OS::FindEx::find_files_recurse( 'x:/pipedev/tools/dcc/current/max2009/scripts/*.ms' ) analyser = ProjectUtil::MaxScriptFileDependencyAnalyser.new( ) dependencies = ProjectUtil::code_dependency_analyser( files, analyser ) dependencies.each do |filename, deps| puts "#{filename} dependencies:" deps.each do |dep| puts "\t#{dep}" end end puts "#{dependencies.size} files with dependencies." rescue Exception => ex puts "Unhandled exception: #{ex.message}" puts ex.backtrace.join( "\n" ) end end # code_dependency_analyser.rb