| #!/usr/bin/ruby | 
 | # | 
 | # Find unused resources in all the apps found recursively under the current directory | 
 | # Usage: | 
 | #   find_unused_resources.rb [-html] | 
 | # | 
 | # If -html is specified, the output will be HTML, otherwise it will be plain text | 
 | # | 
 | # Author: cbeust@google.com | 
 |  | 
 | require 'find' | 
 |  | 
 | debug = false | 
 |  | 
 | @@stringIdPattern = Regexp.new("name=\"([@_a-zA-Z0-9 ]*)\"") | 
 | @@layoutIdPattern = Regexp.new("android:id=\".*id/([_a-zA-Z0-9]*)\"") | 
 |  | 
 | @@stringXmlPatterns = [ | 
 |   Regexp.new("@string/([_a-zA-Z0-9]*)"), | 
 |   Regexp.new("@array/([_a-zA-Z0-9]*)"), | 
 | ] | 
 |  | 
 | @@javaIdPatterns = [ | 
 |   Regexp.new("R.id.([_a-zA-Z0-9]+)"), | 
 |   Regexp.new("R.string.([_a-zA-Z0-9]+)"), | 
 |   Regexp.new("R.array.([_a-zA-Z0-9]+)"), | 
 |   Regexp.new("R.color.([_a-zA-Z0-9]+)"), | 
 |   Regexp.new("R.configVarying.([_a-zA-Z0-9]+)"), | 
 |   Regexp.new("R.dimen.([_a-zA-Z0-9]+)"), | 
 | ] | 
 |  | 
 |  | 
 | @@appDir = "partner/google/apps/Gmail" | 
 |  | 
 | def findResDirectories(root) | 
 |   result = Array.new | 
 |   Find.find(root) do |path| | 
 |     if FileTest.directory?(path) | 
 |       if File.basename(path) == "res" | 
 |         result << path | 
 |       else | 
 |         next | 
 |       end | 
 |     end | 
 |   end | 
 |   result | 
 | end | 
 |  | 
 | class UnusedResources | 
 |   attr_accessor :appDir, :unusedLayoutIds, :unusedStringIds | 
 | end | 
 |  | 
 | class FilePosition | 
 |   attr_accessor :file, :lineNumber | 
 |  | 
 |   def initialize(f, ln) | 
 |     @file = f | 
 |     @lineNumber = ln | 
 |   end | 
 |  | 
 |   def to_s | 
 |     "#{file}:#{lineNumber}" | 
 |   end | 
 |  | 
 |   def <=>(other) | 
 |     if @file == other.file | 
 |       @lineNumber - other.lineNumber | 
 |     else | 
 |       @file <=> other.file | 
 |     end | 
 |   end | 
 | end | 
 |  | 
 |  | 
 | def findAllOccurrences(re, string) | 
 |   result = Array.new | 
 |  | 
 |   s = string | 
 |   matchData = re.match(s) | 
 |   while (matchData) | 
 |     result << matchData[1].to_s | 
 |     s = s[matchData.end(1) .. -1] | 
 |     matchData = re.match(s) | 
 |   end | 
 |  | 
 |   result | 
 | end | 
 |  | 
 | @@globalJavaIdUses = Hash.new | 
 |  | 
 | def recordJavaUses(glob) | 
 |   Dir.glob(glob).each { |filename| | 
 |     File.open(filename) { |file| | 
 |       file.each { |line| | 
 | 	@@javaIdPatterns.each { |re| | 
 |           findAllOccurrences(re, line).each { |id| | 
 |             @@globalJavaIdUses[id] = FilePosition.new(filename, file.lineno) | 
 | 	  } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 | end | 
 |  | 
 | def findUnusedResources(dir) | 
 |   javaIdUses = Hash.new | 
 |   layouts = Hash.new | 
 |   strings = Hash.new | 
 |   xmlIdUses = Hash.new | 
 |  | 
 |   Dir.glob("#{dir}/res/**/*.xml").each { |filename| | 
 |     if ! (filename =~ /attrs.xml$/) | 
 |       File.open(filename) { |file| | 
 |         file.each { |line| | 
 |           findAllOccurrences(@@stringIdPattern, line).each {|id| | 
 |             strings[id] = FilePosition.new(filename, file.lineno) | 
 |           } | 
 |           findAllOccurrences(@@layoutIdPattern, line).each {|id| | 
 |             layouts[id] = FilePosition.new(filename, file.lineno) | 
 |           } | 
 |           @@stringXmlPatterns.each { |re| | 
 |             findAllOccurrences(re, line).each {|id| | 
 |               xmlIdUses[id] = FilePosition.new(filename, file.lineno) | 
 |             } | 
 |           } | 
 |         } | 
 |       } | 
 |     end | 
 |   } | 
 |   | 
 |   Dir.glob("#{dir}/AndroidManifest.xml").each { |filename| | 
 |     File.open(filename) { |file| | 
 |       file.each { |line| | 
 |         @@stringXmlPatterns.each { |re| | 
 |           findAllOccurrences(re, line).each {|id| | 
 |             xmlIdUses[id] = FilePosition.new(filename, file.lineno) | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   recordJavaUses("#{dir}/src/**/*.java") | 
 |  | 
 |   @@globalJavaIdUses.each_pair { |id, file| | 
 |     layouts.delete(id) | 
 |     strings.delete(id) | 
 |   } | 
 |  | 
 |   javaIdUses.each_pair { |id, file| | 
 |     layouts.delete(id) | 
 |     strings.delete(id) | 
 |   } | 
 |  | 
 |   xmlIdUses.each_pair { |id, file| | 
 |     layouts.delete(id) | 
 |     strings.delete(id) | 
 |   } | 
 |  | 
 |   result = UnusedResources.new | 
 |   result.appDir = dir | 
 |   result.unusedLayoutIds = layouts | 
 |   result.unusedStringIds = strings | 
 |  | 
 |   result | 
 | end | 
 |  | 
 | def findApps(dir) | 
 |   result = Array.new | 
 |   Dir.glob("#{dir}/**/res").each { |filename| | 
 |     a = filename.split("/") | 
 |     result << a.slice(0, a.size-1).join("/") | 
 |   } | 
 |   result | 
 | end | 
 |  | 
 | def displayText(result) | 
 |   result.each { |unusedResources| | 
 |     puts "=== #{unusedResources.appDir}" | 
 |  | 
 |     puts "----- Unused layout ids" | 
 |     unusedResources.unusedLayoutIds.sort { |id, file| id[1] <=> file[1] }.each {|f| | 
 |       puts "    #{f[0]} #{f[1]}" | 
 |     } | 
 |  | 
 |   | 
 |     puts "----- Unused string ids" | 
 |     unusedResources.unusedStringIds.sort { |id, file| id[1] <=> file[1] }.each {|f| | 
 |       puts "    #{f[0]} #{f[1]}" | 
 |     } | 
 |   | 
 |   } | 
 | end | 
 |  | 
 | def displayHtmlUnused(unusedResourceIds, title) | 
 |  | 
 |   puts "<h3>#{title}</h3>" | 
 |   puts "<table border='1'>" | 
 |   unusedResourceIds.sort { |id, file| id[1] <=> file[1] }.each {|f| | 
 |     puts "<tr><td><b>#{f[0]}</b></td> <td>#{f[1]}</td></tr>" | 
 |   } | 
 |   puts "</table>" | 
 | end | 
 |  | 
 | def displayHtml(result) | 
 |   title = "Unused resources as of #{Time.now.localtime}" | 
 |   puts "<html><header><title>#{title}</title></header><body>" | 
 |  | 
 |   puts "<h1><p align=\"center\">#{title}</p></h1>" | 
 |   result.each { |unusedResources| | 
 |     puts "<h2>#{unusedResources.appDir}</h2>" | 
 |     displayHtmlUnused(unusedResources.unusedLayoutIds, "Unused layout ids") | 
 |     displayHtmlUnused(unusedResources.unusedStringIds, "Unused other ids") | 
 |   } | 
 |   puts "</body>" | 
 | end | 
 |  | 
 | result = Array.new | 
 |  | 
 | recordJavaUses("java/android/**/*.java") | 
 |  | 
 | if debug | 
 |   result << findUnusedResources("apps/Browser") | 
 | else  | 
 |   findApps(".").each { |appDir| | 
 |     result << findUnusedResources(appDir) | 
 |   } | 
 | end | 
 |  | 
 | if ARGV[0] == "-html" | 
 |   displayHtml result | 
 | else | 
 |   displayText result | 
 | end | 
 |  |