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