Permalink
Fetching contributors…
Cannot retrieve contributors at this time
517 lines (415 sloc) 15 KB
require 'rake'
################################################################################################
# constants
ROOT_DIR = File.expand_path('.')
FINDER_DIR = '/System/Library/CoreServices/Finder.app'
FINDER_RESOURCES_DIR = File.join(FINDER_DIR, 'Contents/Resources')
PLUGIN_RESOURCES_DIR = File.join(ROOT_DIR, 'plugin')
INSTALLER_RESOURCES_DIR = File.join(ROOT_DIR, 'installer')
ENGLISH_LPROJ = File.join(PLUGIN_RESOURCES_DIR, 'English.lproj')
TOTALFINDER_PLUGIN_SOURCES = File.join(ROOT_DIR, '..', '..', 'totalfinder-plugin', 'plugin')
TOTALFINDER_FEATURES_SOURCES = File.join(ROOT_DIR, '..', '..', 'totalfinder-features')
################################################################################################
# dependencies
begin
require 'colored'
rescue LoadError
raise 'You must "gem install colored" to use terminal colors'
end
################################################################################################
# helpers
def die(msg, status=1)
puts "Error[#{status||$?}]: #{msg}".red
exit status||$?
end
def sys(cmd)
puts "> #{cmd}".yellow
system(cmd)
end
################################################################################################
# routines
def get_list_of_features(filter=nil)
filter = "*" unless filter
features = []
Dir.glob(File.join(TOTALFINDER_FEATURES_SOURCES, filter)) do |file|
if File.directory?(file) and File.exists? File.join(file, File.basename(file)+".xcodeproj") then
features << File.basename(file)
end
end
features.uniq.reject{|x| x=="FeaturesFramework"}
end
def extract_code_strings(folder)
strings = ""
Dir.chdir(folder) do
strings << `ack -oh '\\$\\s*\\(@".*?"\\)'` # `ack -oh '\$\s*\(@".*?"\)'`
strings << `ack -oh '\\$\\$\\s*\\(@".*?"\\)'` # `ack -oh '\$\$\s*\(@".*?"\)'`
end
strings = strings.split("\n").map do |s|
s =~ /(\".*\")/
$1
end
strings.uniq
end
def extract_ui_strings(folder, xib_mask)
strings = ""
Dir.chdir(folder) do
strings << `ack -o -G #{xib_mask} '\\^.*?\\<'` # search only xibs for ^SOME STRING
end
strings = strings.split("\n").map do |s|
s =~ /\^(.*)\</
'"'+$1+'"'
end
strings.uniq
end
def parse_strings_file(filename)
lines = []
if File.exists? filename then
File.open(filename, "r") do |f|
f.each do |line|
lines << line
end
end
end
lines
end
def update_english_strings(project, src_folder)
removed_count = 0
added_count = 0
target = File.join(ENGLISH_LPROJ, "#{project}.strings")
code_strings = extract_code_strings(src_folder)
strings = parse_strings_file(target)
# comment out all existing strings as REMOVED
strings.map! do |line|
if (line[0...1]=='"') then
line = "/* REMOVED "+line.strip+" */\n" # ***
removed_count += 1
end
line
end
# go through code strings and try to unmark them in the file
epilogue_emmited = false
code_strings.each do |code|
found = false
strings.map! do |line|
if line =~ /^\/\* REMOVED/
if line.index code then
line.gsub!("REMOVED", "DUPLICIT") if found
line = line[11..-5] + "\n" unless found # see ***
found = true
removed_count -= 1
end
end
line
end
unless found then
# put new strings at the end of the file
unless epilogue_emmited then
strings << "\n"
strings << "/* NEW STRINGS - TODO: SORT THEM IN OR CREATE A NEW SECTION */\n"
epilogue_emmited = true
end
strings << code + " = " + code + ";\n"
added_count += 1
end
end
strings << "/* no strings */" if strings.size==0
File.open(target, "w") do |f|
f << strings.join
end
puts " #{"+#{added_count}".blue} #{"-#{removed_count}".red} of #{strings.size} in #{target}"
end
def update_english_localizable_strings(project, xib_mask)
removed_count = 0
added_count = 0
target = File.join(ENGLISH_LPROJ, "#{project}UI.strings")
ui_strings = extract_ui_strings(PLUGIN_RESOURCES_DIR, xib_mask)
localizable_strings = parse_strings_file(target)
# comment out all existing strings as REMOVED
localizable_strings.map! do |line|
if (line[0...1]=='"') then
line = "/* REMOVED "+line.strip+" */\n" # ***
removed_count += 1
end
line
end
# go through code strings and try to unmark them in the file
epilogue_emmited = false
ui_strings.each do |code|
found = false
localizable_strings.map! do |line|
if line =~ /^\/\* REMOVED/
if line.index code then
line.gsub!("REMOVED", "DUPLICIT") if found
line = line[11..-5] + "\n" unless found # see ***
found = true
removed_count -= 1
end
end
line
end
unless found then
# put new strings at the end of the file
unless epilogue_emmited then
localizable_strings << "\n"
localizable_strings << "/* NEW STRINGS - TODO: SORT THEM IN OR CREATE A NEW SECTION */\n"
epilogue_emmited = true
end
localizable_strings << code + " = " + code + ";\n"
added_count += 1
end
end
localizable_strings << "/* no strings */" if localizable_strings.size==0
File.open(target, "w") do |f|
f << localizable_strings.join
end
puts " #{"+#{added_count}".blue} #{"-#{removed_count}".red} of #{localizable_strings.size} in #{target}"
end
def inprint_strings(source, dest)
strings = parse_strings_file(source)
originals = parse_strings_file(dest)
# transform lang back to english
index = 0
strings.map! do |line|
index+=1
next line unless (line.strip[0...1]=='"')
line =~ /^\s*?(".*")\s*?=\s*?(".*")\s*?;\s*?/
die "syntax error in " + source.blue+":"+index.to_s unless $1
line = $1 + " = " + $1 + ";\n";
line
end
# replace translations we already know from previsous version
index = 0
originals.each do |original|
index+=1
next unless (original.strip[0...1]=='"')
original =~ /^\s*?(".*")\s*?=\s*?(".*")\s*?;(.*)$/
needle = $1
haystack = $2
rest = $3
die "syntax error in " + dest.blue+":"+index.to_s unless $1 and $2
found = false
strings.map! do |line|
if (line.index needle) == 0 then
line = needle + " = " + haystack + ";" + rest + "\n";
found = true
end
line
end
end
File.open(dest, "w") do |f|
f << strings.join
end
strings
end
def propagate_english_to_cwd
puts Dir.pwd.blue
total = 0
Dir.glob(File.join(ENGLISH_LPROJ, "*.strings")) do |file|
puts " #{File.basename(file)}".yellow
total += inprint_strings(file, File.join(Dir.pwd, File.basename(file))).size
end
puts " -> "+total.to_s.green+" strings processed"
end
def propagate_from_english_to_other_lprojs
glob = ENV["to"] || "*.lproj"
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, glob)) do |dir|
Dir.chdir dir do
propagate_english_to_cwd
end
end
end
def create_localizations_for_project
glob = ENV["to"] || "*.lproj"
project = ENV["project"] || die("Project name not defined")
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, glob)) do |dir|
Dir.chdir dir do
File.open(File.join(dir, project + ".strings"), "w") do |f|
f << "/* no strings */"
end
File.open(File.join(dir, project + "UI.strings"), "w") do |f|
f << "/* no strings */"
end
end
end
end
def exec_cmd_in_lprojs(cmd)
glob = ENV["to"] || "*.lproj"
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, glob)) do |dir|
puts dir.blue
Dir.chdir dir do
sys(cmd)
end
end
end
def validate_strings_file path
lines = parse_strings_file(path)
in_multi_line_comment = false
counter = 0
count = lines.size
lines.each do |line|
counter += 1
if in_multi_line_comment and line =~ /.*\*\/\w*$/
in_multi_line_comment = false
next
end
next if in_multi_line_comment
next if line =~ /^".*?"\s*=\s*".*?";\s*$/
next if line =~ /^".*?"\s*=\s*".*?";\s*\/\*.*?\*\/$/
next if line =~ /^".*?"\s*=\s*".*?";\s*\/\/.*?$/
next if line =~ /^\/\*.*?\*\/$/
next if line =~ /^$/
# last line may be without new-line character
next if counter==count and line =~ /^".*?"\s*=\s*".*?";\s*/
next if counter==count and line =~ /^".*?"\s*=\s*".*?";\s*\/\*.*?\*\//
next if counter==count and line =~ /^".*?"\s*=\s*".*?";\s*\/\/.*?$/
if line =~ /^\/\*[^\*]*/ then
in_multi_line_comment = true
next
end
puts "line ##{counter}: unrecognized pattern".red+" (fix rakefile if this is a valid pattern)"
puts line
puts "mate -l #{counter} \"#{path}\"".yellow
return false
end
true
end
def validate_strings_files
begin
require 'cmess/guess_encoding'
rescue LoadError
die 'You must "gem install cmess" to use character encoding detection'
end
glob = ENV["to"] || "*.lproj"
counter = 0
failed = 0
warnings = 0
known_files = []
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, "English.lproj", "*")) do |path|
known_files << File.basename(path)
end
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, "*.lproj")) do |dir|
unrecognized_files = []
missing_files = known_files.dup
Dir.glob(File.join(dir, "*")) do |path|
file = File.basename(path)
if missing_files.include?(file) then
missing_files.delete(file)
else
unrecognized_files << file
end
end
if (!missing_files.empty? or !unrecognized_files.empty?) then
warnings += 1
puts "in " + dir.blue + ":"
if (!missing_files.empty?) then
puts " missing files: " + missing_files.join(", ")
end
if (!unrecognized_files.empty?) then
puts " unrecognized files: " + unrecognized_files.join(", ")
end
end
end
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, glob, "*.strings")) do |path|
counter += 1
input = File.read path
charset = CMess::GuessEncoding::Automatic.guess(input)
ok = ((validate_strings_file path) and (charset=="ASCII" or charset=="UTF-8"))
puts charset.magenta+" "+path.blue+" "+"ok".yellow if ok
puts charset.magenta+" "+path.blue+" "+"failed".red unless ok
failed +=1 unless ok
end
puts "-----------------------------------"
puts "checked "+"#{counter} files".magenta+" and "+(failed>0 ? ("#{failed} failed".red) : ("all is ok".yellow)) + (warnings>0?(" [#{warnings} warnings]".green):(""))
end
def stub_installer_lprojs
glob = "*.lproj"
english_source = File.join(INSTALLER_RESOURCES_DIR, "English.lproj")
die("need #{english_source}!") unless File.exists?(english_source)
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, glob)) do |dir|
name = File.basename(dir)
next if name == "English.lproj"
full_path = File.join(INSTALLER_RESOURCES_DIR, name)
next if File.exists?(full_path) # already have it
puts "Creating stub " + full_path.blue
sys("cp -r \"#{english_source}\" \"#{full_path}\"")
end
end
################################################################################################
# tasks
desc "switch /Applications/TotalFinder.app into dev mode"
task :dev do
sys("./dev.sh")
end
desc "switch /Applications/TotalFinder.app into non-dev mode"
task :undev do
sys("./undev.sh")
end
desc "compile XIBs to NIBs (you need this only when you modify UI in the InterfaceBuilder)"
task :compile do
sys("./compile.sh")
end
desc "restart Finder.app"
task :restart do
sys("./restart.sh")
end
desc "normalize Finder.app so it contains all our language folders (run with sudo)"
task :normalize do
lprojs = File.join(PLUGIN_RESOURCES_DIR, '*.lproj')
Dir.glob(lprojs) do |folder|
dir = File.join(FINDER_RESOURCES_DIR, File.basename(folder))
if File.exists? dir then
puts dir.blue + " exists".yellow
else
if !sys("mkdir -p \"#{dir}\"") then
die("Unable to create a folder. Hint: you should run this as sudo rake normalize")
end
puts dir.blue + " created".green
end
end
end
desc "cherrypicks strings from sources and applies missing strings to English.lproj"
task :cherrypick do
die "install ack 1.92+ | for example via homebrew:> brew install ack" if `which ack`==""
features = get_list_of_features()
totalfinder_xibs = []
Dir.glob(File.join(PLUGIN_RESOURCES_DIR, "*.xib")) do |file|
name = File.basename(file, ".xib")
totalfinder_xibs << name unless features.include? name
end
totalfinder_filter = "'(#{totalfinder_xibs.join("|")})\\.xib'"
if File.exists? TOTALFINDER_PLUGIN_SOURCES then
puts "Processing TotalFinder...".yellow
update_english_strings("TotalFinder", TOTALFINDER_PLUGIN_SOURCES)
update_english_localizable_strings("TotalFinder", totalfinder_filter) # process just TotalFinder's xibs
end
features.each do |feature|
feature_dir = File.join(TOTALFINDER_FEATURES_SOURCES, feature)
if File.exists? feature_dir then
puts "Processing #{feature}...".yellow
update_english_strings(feature, feature_dir)
feature_filter = "'(#{feature})\\.xib'"
update_english_localizable_strings(feature, feature_filter) # process just feature's xibs
end
end
end
desc "propagates structure of English.lproj to all other language folders while keeping already translated strings"
task :propagate do
propagate_from_english_to_other_lprojs
end
desc "make stub lproj folders for installer, creates all which exist in plugin"
task :stub do
stub_installer_lprojs
end
desc "exec command in all lproj folders"
task :exec do
exec_cmd_in_lprojs(ENV["cmd"] || "ls")
end
desc "validates all strings files and checks them for syntax errors"
task :validate do
validate_strings_files
end
desc "validates all strings files and checks them for syntax errors"
task :create_localization do
create_localizations_for_project
end
task :default => :restart