<commit_before># encoding: UTF-8
# Have fun. Use at your own risk.
# Copyright (c) 2014 <NAME>

require 'nokogiri'
require 'ostruct'
require 'optparse'

module StoryboardLint
  class StoryboardScanner
    def initialize(src_root)
      if !File.directory?(src_root)
        raise ArgumentError, "The directory '#{src_root}' does not exist."
      end
      
      @src_root = src_root
      @scan_performed = false    
    end
    
    def storyboard_files
      return @sb_files if @sb_files
  
      # find all storyboard files...
      @sb_files = Dir.glob(File.join(@src_root, "**/*.storyboard"))
    end
    
    def xib_files
      return @xib_files if @xib_files
  
      # find all XIB files...
      @xib_files = Dir.glob(File.join(@src_root, "**/*.xib"))
    end
    
    def segue_ids
      scan_files
      @segue_ids
    end
    
    def storyboard_ids
      scan_files
      @storyboard_ids
    end
    
    def reuse_ids
      scan_files
      @reuse_ids
    end
    
    def custom_class_names
      scan_files
      @custom_class_names
    end
    
    private
    
    def scan_files
      if !@scan_performed
        @segue_ids ||= []
        @storyboard_ids ||= []
        @reuse_ids ||= []
        @custom_class_names = []
    
        storyboard_files.each do |sb_file|
          sb_source = File.open(sb_file)
          doc = Nokogiri::XML(sb_source)
          doc.xpath('//segue').each do |element|
            seg_id = element.attr('identifier')
            @segue_ids << {:file => sb_file, :id => seg_id.to_s} if seg_id
          end

          @storyboard_ids += doc.xpath("//@storyboardIdentifier").to_a.map {|match| {:file => sb_file, :id => match.to_s}}
          @reuse_ids += doc.xpath("//@reuseIdentifier").to_a.map {|match| {:file => sb_file, :id => match.to_s}}
          @custom_class_names += doc.xpath("//@customClass").to_a.map {|match| {:file => sb_file, :class_name => match.to_s}}
        end
        
        xib_files.each do |xib_file|
          xib_source = File.open(xib_file)
          doc = Nokogiri::XML(xib_source)

          @reuse_ids += doc.xpath("//@reuseIdentifier").to_a.map {|match| {:file => xib_file, :id => match.to_s}}
        end
        
        @scan_performed = true
      end
    end
  end

  class SourceScanner    
    def initialize(src_root, matcher, additional_sources = nil)
      if !File.directory?(src_root)
        raise ArgumentError, "The directory '#{src_root}' does not exist."
      end
      
      if !matcher
        raise ArgumentError, "The matcher cannot be nil."
      end
      
      @additional_sources = additional_sources
      @matcher = matcher
      @src_root = src_root
      @scan_performed = false    
    end
    
    def source_files
      return @source_files if @source_files
  
      # find all *.h, *.c, *.m and *.mm files
      match_string = "**/*.{h,c,m,mm}"
      @source_files = Dir.glob(File.join(@src_root, match_string))
      
      if @additional_sources && @additional_sources.size > 0
        @additional_sources.each do |source_path|
          absolute_path = ''
          if source_path.start_with?("/")
            #absolute path
            absolute_path = source_path
          else
            #relative to src_root
            absolute_path = File.join(@src_root, source_path)
          end
          
          if File.directory?(absolute_path)
            @source_files += Dir.glob(File.join(absolute_path, match_string))
          else
            puts "warning: additional source directory '#{absolute_path}' does not exist!"
          end
        end
      end
      
      if @source_files and @source_files.size > 0
        @source_files.select! {|sf| File.file?(sf)}
      end
      
      @source_files
    end
    
    def segue_ids
      scan_files
      @segue_ids
    end
    
    def storyboard_ids
      scan_files
      @storyboard_ids
    end
    
    def reuse_ids
      scan_files
      @reuse_ids
    end
    
    def class_names
      scan_files
      @class_names
    end
    
    private
    
    def scan_files
      if !@scan_performed
        @class_names ||= []
        @segue_ids ||= []
        @storyboard_ids ||= []
        @reuse_ids ||= []

        source_files.each do |source_file|
          File.readlines(source_file, :encoding => 'UTF-8').each_with_index do |line, idx|
            # class names
            line.scan(@matcher.class_regex).each do |match|
              @class_names << {:file => source_file, :line => idx + 1, :class_name => match[0]}
            end
            <commit_msg>Encode each line before scanning<commit_after># encoding: UTF-8
# Have fun. Use at your own risk.
# Copyright (c) 2014 <NAME>

require 'nokogiri'
require 'ostruct'
require 'optparse'

module StoryboardLint
  class StoryboardScanner
    def initialize(src_root)
      if !File.directory?(src_root)
        raise ArgumentError, "The directory '#{src_root}' does not exist."
      end
      
      @src_root = src_root
      @scan_performed = false    
    end
    
    def storyboard_files
      return @sb_files if @sb_files
  
      # find all storyboard files...
      @sb_files = Dir.glob(File.join(@src_root, "**/*.storyboard"))
    end
    
    def xib_files
      return @xib_files if @xib_files
  
      # find all XIB files...
      @xib_files = Dir.glob(File.join(@src_root, "**/*.xib"))
    end
    
    def segue_ids
      scan_files
      @segue_ids
    end
    
    def storyboard_ids
      scan_files
      @storyboard_ids
    end
    
    def reuse_ids
      scan_files
      @reuse_ids
    end
    
    def custom_class_names
      scan_files
      @custom_class_names
    end
    
    private
    
    def scan_files
      if !@scan_performed
        @segue_ids ||= []
        @storyboard_ids ||= []
        @reuse_ids ||= []
        @custom_class_names = []
    
        storyboard_files.each do |sb_file|
          sb_source = File.open(sb_file)
          doc = Nokogiri::XML(sb_source)
          doc.xpath('//segue').each do |element|
            seg_id = element.attr('identifier')
            @segue_ids << {:file => sb_file, :id => seg_id.to_s} if seg_id
          end

          @storyboard_ids += doc.xpath("//@storyboardIdentifier").to_a.map {|match| {:file => sb_file, :id => match.to_s}}
          @reuse_ids += doc.xpath("//@reuseIdentifier").to_a.map {|match| {:file => sb_file, :id => match.to_s}}
          @custom_class_names += doc.xpath("//@customClass").to_a.map {|match| {:file => sb_file, :class_name => match.to_s}}
        end
        
        xib_files.each do |xib_file|
          xib_source = File.open(xib_file)
          doc = Nokogiri::XML(xib_source)

          @reuse_ids += doc.xpath("//@reuseIdentifier").to_a.map {|match| {:file => xib_file, :id => match.to_s}}
        end
        
        @scan_performed = true
      end
    end
  end

  class SourceScanner    
    def initialize(src_root, matcher, additional_sources = nil)
      if !File.directory?(src_root)
        raise ArgumentError, "The directory '#{src_root}' does not exist."
      end
      
      if !matcher
        raise ArgumentError, "The matcher cannot be nil."
      end
      
      @additional_sources = additional_sources
      @matcher = matcher
      @src_root = src_root
      @scan_performed = false    
    end
    
    def source_files
      return @source_files if @source_files
  
      # find all *.h, *.c, *.m and *.mm files
      match_string = "**/*.{h,c,m,mm}"
      @source_files = Dir.glob(File.join(@src_root, match_string))
      
      if @additional_sources && @additional_sources.size > 0
        @additional_sources.each do |source_path|
          absolute_path = ''
          if source_path.start_with?("/")
            #absolute path
            absolute_path = source_path
          else
            #relative to src_root
            absolute_path = File.join(@src_root, source_path)
          end
          
          if File.directory?(absolute_path)
            @source_files += Dir.glob(File.join(absolute_path, match_string))
          else
            puts "warning: additional source directory '#{absolute_path}' does not exist!"
          end
        end
      end
      
      if @source_files and @source_files.size > 0
        @source_files.select! {|sf| File.file?(sf)}
      end
      
      @source_files
    end
    
    def segue_ids
      scan_files
      @segue_ids
    end
    
    def storyboard_ids
      scan_files
      @storyboard_ids
    end
    
    def reuse_ids
      scan_files
      @reuse_ids
    end
    
    def class_names
      scan_files
      @class_names
    end
    
    private
    
    def scan_files
      if !@scan_performed
        @class_names ||= []
        @segue_ids ||= []
        @storyboard_ids ||= []
        @reuse_ids ||= []

        source_files.each do |source_file|
          File.readlines(source_file, :encoding => 'UTF-8').each_with_index do |line, idx|
            # class names
            line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
            line.scan(@matcher.class_regex).each do |match|
              @class_names << {:file => source_file, :line => idx + 1, :class_name => match[0]}
            end
            
{'fraction_non_alphanumeric': 0.10802603036876356, 'fraction_numerical': 0.00227765726681128, 'mean_word_length': 1.9450654742893645, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '881035', 'n_tokens_mistral': 3012, 'n_tokens_neox': 2963, 'n_words': 653}