lib/missing_t.rb in missing_t-0.3.2 vs lib/missing_t.rb in missing_t-0.4.0

- old
+ new

@@ -1,18 +1,32 @@ require "yaml" -require "optparse" -require "ostruct" -require "forwardable" +#TODO: Should I feel about these 'global' helper functions? +def hashify(strings) + strings.map { |s| s.split('.') }.each_with_object({}) do |segmented_string, h| + segmented_string.each do |segment| + h[segment] ||= {} + h = h[segment] + end + end +end + +def print_hash(h, level) + h.each_pair do |k,v| + puts %(#{" " * (level*2)}#{k}:) + print_hash(v, level+1) + end +end + class Hash # idea snatched from deep_merge in Rails source code def deep_safe_merge(other_hash) self.merge(other_hash) do |key, oldval, newval| oldval = oldval.to_hash if oldval.respond_to?(:to_hash) newval = newval.to_hash if newval.respond_to?(:to_hash) - if oldval.class.to_s == 'Hash' - if newval.class.to_s == 'Hash' + if oldval === Hash + if newval === Hash oldval.deep_safe_merge(newval) else oldval end else @@ -25,84 +39,101 @@ replace(deep_safe_merge(other_hash)) end end -module Helpers - # snatched from rspec source - def colour(text, colour_code) - "#{colour_code}#{text}\e[0m" - end - - def green(text); colour(text, "\e[32m"); end - def red(text); colour(text, "\e[31m"); end - def magenta(text); colour(text, "\e[35m"); end - def yellow(text); colour(text, "\e[33m"); end - def blue(text); colour(text, "\e[34m"); end - -end - class MissingT class FileReader def read(file) open(File.expand_path(file), "r") do |f| yield f.read end end end - VERSION = "0.3.2" + VERSION = "0.4.0" - include Helpers - - def initialize(reader) - @reader = reader + def initialize(options={}) + @reader = options.fetch(:reader, FileReader.new) + @languages = options[:languages] + @path = options[:path] end - def parse_options(args) - @options = OpenStruct.new - @options.prefix = nil - opts = OptionParser.new do |opts| - opts.on("-f", "--file FILE_OR_DIR", - "look for missing translations in files under FILE_OR_DIR", - "(if a file is given, only look in that file)") do |path| - @options.path = path - end - end + def run + missing_translations = collect + missing_message_strings = missing_translations.values.map { |ms| hashify(ms) } - opts.on_tail("-h", "--help", "Show this message") do - puts opts - exit + missing = missing_message_strings.each_with_object({}) do |h, all_message_strings| + all_message_strings.deep_safe_merge!(h) end - opts.on_tail("--version", "Show version") do - puts VERSION - exit + missing.each do |language, missing_for_language| + puts + puts "#{language}:" + print_hash(missing_for_language, 1) end + end - opts.parse!(args) + def collect + ts = translation_keys + #TODO: If no translation keys were found and the languages were not given explicitly + # issue a warning and bail out + languages = @languages ? @languages : ts.keys + get_missing_translations(translation_keys, translation_queries, languages) end + def get_missing_translations(keys, queries, languages) + languages.each_with_object({}) do |lang, missing| + get_missing_translations_for_lang(keys, queries, lang).each do |file, queries| + missing[file] ||= [] + missing[file].concat(queries).uniq! + end + end + end + def translation_keys locales_pathes = ["config/locales/**/*.yml", "vendor/plugins/**/config/locales/**/*yml", "vendor/plugins/**/locale/**/*yml"] locales_pathes.each_with_object({}) do |path, translations| Dir.glob(path) do |file| t = open(file) { |f| YAML.load(f.read) } translations.deep_safe_merge!(t) end end end + def translation_queries + files_with_i18n_queries.each_with_object({}) do |file, queries| + queries_in_file = extract_i18n_queries(file) + if queries_in_file.any? + queries[file] = queries_in_file + end + end + #TODO: remove duplicate queries across files + end + + def has_translation?(keys, lang, query) + i18n_label(lang, query).split('.').each do |segment| + return false unless segment =~ /#\{.*\}/ or (keys.respond_to?(:key?) and keys.key?(segment)) + keys = keys[segment] + end + true + end + def files_with_i18n_queries - if path = @options.path - path = path[0...-1] if path[-1..-1] == '/' - [ - Dir.glob("#{path}/**/*.erb"), - Dir.glob("#{path}/**/*.haml"), - Dir.glob("#{path}/**/*.rb") - ] + if @path + path = File.expand_path(@path) + if File.file?(path) + [@path] + else + path.chomp!('/') + [ + Dir.glob("#{path}/**/*.erb"), + Dir.glob("#{path}/**/*.haml"), + Dir.glob("#{path}/**/*.rb") + ] + end else [ Dir.glob("app/**/*.erb"), Dir.glob("app/**/*.haml"), Dir.glob("app/**/models/**/*.rb"), @@ -114,63 +145,29 @@ def extract_i18n_queries(file) i18n_query_pattern = /[^\w]+(?:I18n\.translate|I18n\.t|translate|t)\s*\((.*?)[,\)]/ i18n_query_no_parens_pattern = /[^\w]+(?:I18n\.translate|I18n\.t|translate|t)\s+(['"])(.*?)\1/ - @reader.read(file) do |content| + @reader.read(File.expand_path(file)) do |content| ([]).tap do |i18n_message_strings| - i18n_message_strings << content.scan(i18n_query_pattern).map { |match| match[0].gsub(/['"\s]/, '') } - i18n_message_strings << content.scan(i18n_query_no_parens_pattern).map { |match| match[1].gsub(/['"\s]/, '') } - end.flatten - end - end - - def translation_queries - files_with_i18n_queries.each_with_object({}) do |file, queries| - queries_in_file = extract_i18n_queries(file) - if queries_in_file.any? - queries[file] = queries_in_file + i18n_message_strings.concat content.scan(i18n_query_pattern).map { |match| match[0].gsub(/['"\s]/, '') } + i18n_message_strings.concat content.scan(i18n_query_no_parens_pattern).map { |match| match[1].gsub(/['"\s]/, '') } end end - #TODO: remove duplicate queries across files end - def has_translation?(keys, lang, query) - i18n_label(lang, query).split('.').each do |segment| - return false unless segment =~ /#\{.*\}/ or (keys.respond_to?(:key?) and keys.key?(segment)) - keys = keys[segment] - end - true - end +private - def get_missing_translations(keys, queries, languages) - languages.each_with_object({}) do |lang, missing| - get_missing_translations_for_lang(keys, queries, lang).each do |file, queries| - missing[file] ||= [] - missing[file].concat(queries).uniq! + def get_missing_translations_for_lang(keys, queries, lang) + queries.map do |file, queries_in_file| + queries_with_no_translation = queries_in_file.reject { |q| has_translation?(keys, lang, q) } + if queries_with_no_translation.any? + [file, queries_with_no_translation.map { |q| i18n_label(lang, q) }] end - end + end.compact end - def find_missing_translations(lang=nil) - ts = translation_keys - get_missing_translations(translation_keys, translation_queries, lang ? [lang] : ts.keys) + def i18n_label(lang, query) + "#{lang}.#{query}" end - - private - def get_missing_translations_for_lang(keys, queries, lang) - queries.map do |file, queries_in_file| - queries_with_no_translation = queries_in_file.select { |q| !has_translation?(keys, lang, q) } - if queries_with_no_translation.empty? - nil - else - [file, queries_with_no_translation.map { |q| i18n_label(lang, q) }] - end - end.compact - - end - - def i18n_label(lang, query) - "#{lang}.#{query}" - end end