|  | #!/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 | 
|  |  |