Class: StringHound

Inherits:
Object
  • Object
show all
Includes:
RegexUtils
Defined in:
lib/string_hound.rb

Overview

Given a directory, StringHound recursively searches the directory heirarchy looking for any hardcoded strings. When found, it prints them to standard out in the form:

<filename>: <line>    <string value>

In speak mode, Stringhound will also insert a suggested i18n conversion of all strings it finds into the file it finds them in, as well as insert the same key and translation into the default yml file

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from RegexUtils

included

Constructor Details

#initialize(dir, opts = nil) ⇒ StringHound

Returns a new instance of StringHound.



31
32
33
34
35
36
# File 'lib/string_hound.rb', line 31

def initialize(dir, opts = nil)
  @directory = dir
  @prize = []
  @command_speak = false
  @interactive = opts && opts[:interactive] ? opts[:interactive] : false
end

Class Attribute Details

.default_ymlObject

Returns the value of attribute default_yml.



27
28
29
# File 'lib/string_hound.rb', line 27

def default_yml
  @default_yml
end

Instance Attribute Details

#command_speakObject

Returns the value of attribute command_speak.



25
26
27
# File 'lib/string_hound.rb', line 25

def command_speak
  @command_speak
end

#fileObject

Returns the value of attribute file.



25
26
27
# File 'lib/string_hound.rb', line 25

def file
  @file
end

#interactiveObject

Returns the value of attribute interactive.



25
26
27
# File 'lib/string_hound.rb', line 25

def interactive
  @interactive
end

#view_fileObject (readonly)

Returns the value of attribute view_file.



24
25
26
# File 'lib/string_hound.rb', line 24

def view_file
  @view_file
end

Instance Method Details

#chew(prsd_arry) ⇒ Object

Get rid of strings that are only whitespace, only digits, or are variable names e.g. wombat_love_id, 55, ”



107
108
109
110
111
112
113
114
# File 'lib/string_hound.rb', line 107

def chew(prsd_arry)
  prsd_arry.select do |parsed_string|
    parsed_string.match(/[\S]/) &&
      parsed_string.match(/[\D]/)  &&
      !parsed_string.match(/txt\.[\w]*\.[\w]*/) &&
      !(parsed_string.match(/[\s]/).nil? && parsed_string.match(/[_-]/))
  end
end

#digest(content) ⇒ Object

Take each piece of found content, search it for embedded variables, construct a new key for the content line and an i18n call for the new content. Key’s mainword is longest word of the string

Returns:

localized_string = I18n.t('txt.admin.file_path.success', :organization => organization)
key              = txt.admin.file_path.success


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/string_hound.rb', line 128

def digest(content)
  vars = find_variables(content)

  cur_path = @file.path.split('/',2).last
  cur_path = cur_path.split('.').first
  cur_path.gsub!('/','.')

  words = content.scan(/\w+/)
  identifier = words[0,5].join('_')

  key_name = "txt.admin." + cur_path + '.' + identifier
  localized_string = "I18n.t('#{key_name}'"

  if vars
    vars.each { |v| localized_string << ", :#{v} => #{v}" }
  end
  localized_string << ")"

  return localized_string, key_name
end

#file_cleanupObject

Close all files and rename tmp file to real source file



253
254
255
256
257
258
259
# File 'lib/string_hound.rb', line 253

def file_cleanup
  if @tmp_file
    @tmp_file.close
    FileUtils.mv(@tmp_file.path, @file.path)
    @tmp_file = nil
  end
end

#howlObject

Print matches to STDOUT



245
246
247
# File 'lib/string_hound.rb', line 245

def howl
  @prize.each { |p| puts "#{p[:filename]} : #{p[:line_number]}\t\t #{p[:value]}" }
end

#huntObject

Iterates through directory and sets up current file to hunt through. Close yml_file if speak was enabled



44
45
46
47
48
49
50
51
52
53
54
# File 'lib/string_hound.rb', line 44

def hunt
  Find.find(@directory) do |f|
    unless FileTest.directory?(f)
      @file = File.open(f, "r")
      @view_file = ['.html', '.erb'].include?(File.extname(f))
      sniff
      @file.close if !@file.closed?
    end
  end
  @yml_file.close if @yml_file
end

#sniffObject

Grabs content line by line from file and parses it. If speak is enabled, cleanup associated files after it runs



63
64
65
66
67
68
69
70
71
# File 'lib/string_hound.rb', line 63

def sniff
  @file.each_line do |l|
    taste(l)
    @content_arry.each { |m| @prize << {:filename => @file.path, :line_number => @file.lineno, :value => m } }
    speak(l)
  end

  file_cleanup
end

#speak(line) ⇒ Object

If content is present, generate 18n for it and add it to tmp source file and yml file. Othewise pass through original txt to tmp file.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/string_hound.rb', line 156

def speak(line)
  return unless @command_speak

  f_name = File.basename(@file.path)
  @tmp_file ||= Tempfile.new(f_name)
  @yml_file ||= File.open(self.class.default_yml, "a+")


  if !@content_arry.empty?
    replacement_arry=[]
    @content_arry.each do |content|
      i18n_string, key_name = digest(content)
      replacement_arry << [i18n_string, content, key_name]
    end

    speak_source_file(line, replacement_arry)
    if !@interactive || @localize_now
      speak_yml(replacement_arry)
    end
  else
    @tmp_file.write(line)
  end
end

#speak_source_file(line, replacement_arry) ⇒ Object

Construct a diff like format in tmp file for i18n string



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/string_hound.rb', line 186

def speak_source_file(line, replacement_arry)

  localized_line = line.dup
  replacement_arry.each do |i18n_string, content|
    replacement_string = @html_text ? "<%= "+ i18n_string + " %>" : i18n_string
    localized_line.gsub!(content, replacement_string)
  end

  if @interactive
    write_diffs(STDOUT, line, localized_line)
    speak_to_me
    @localize_now ? @tmp_file.write(localized_line) : @tmp_file.write(line)
  else
    write_diffs(@tmp_file, line, localized_line)
  end
end

#speak_to_meObject

Ask whether to localize the string now or not



207
208
209
210
211
212
213
# File 'lib/string_hound.rb', line 207

def speak_to_me
  begin
    puts "Localize string now? (y/n)"
    answer = STDIN.gets
  end while !answer.match(/^(y|n)/)
  @localize_now =  answer.include?("y")? true : false
end

#speak_yml(replacement_arry) ⇒ Object

Add translation key to yml file



227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/string_hound.rb', line 227

def speak_yml(replacement_arry)
  replacement_arry.each do |i8n_string, content, key_name|
    quoteless_content = content.gsub(/["']/,'')
    yml_string  = "\n  - translation:\n"
    yml_string << "      key: \"#{key_name}\"\n"
    yml_string << "      title: \"#{quoteless_content} label\"\n"
    yml_string << "      value: \"#{quoteless_content}\"\n"

    @yml_file.write("\n<<<<<<<<<<\n") unless @localize_now
    @yml_file.write(yml_string)
    @yml_file.write(">>>>>>>>>>\n") unless @localize_now
  end
end

#taste(line) ⇒ Object

Parse engine



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/string_hound.rb', line 76

def taste(line)
  @content_arry = out = []
  #Note: this is temporary, will create a new class for 'line' to handle this all more cleanly
  @html_text = false

  if view_file
    return if is_erb_txt(line)
    return if is_javascript(line)

    if m = find_printed_erb(line)
      out = parse_for_strings(m[0])
    else
      result = Nokogiri::HTML(line)
      @html_text = true
      out = result.text().empty? ? out : result.text()
    end
  elsif result = inline_strings(line)
    out = result
  else
    out = parse_for_strings(line)
  end

  @content_arry = chew(out)
end

#write_diffs(output_via, line, localized_line) ⇒ Object



216
217
218
219
220
221
222
# File 'lib/string_hound.rb', line 216

def write_diffs(output_via, line, localized_line)
  output_via.write("<<<<<<<<<<\n")
  output_via.write("#{localized_line}\n")
  output_via.write("==========\n")
  output_via.write(line)
  output_via.write(">>>>>>>>>>\n")
end