#!/usr/bin/ruby
#
# get-ruby-strings: interactively process ruby source file for gettext
#
# get-ruby-strings.rb
#
# This program process a ruby source file, and output a modified file to
# .modified
# The following modifications are done to string literals enclosed with '' or ""; the
# program will prompt for each string asking whether they will be modified. If you
# answer "gettextize"
#
# * The string is wrapped with _( )
# * If the string is enclosed with "", and it contains embedded expressions with the
# #{ } notation, such as "The file is #{filename}", the string will be rewritten in
# string modulo syntax such as _("The file is %{filename}") % {:filename => filename}
# Note that this form of String#% taking a hash argument is available with ruby-gettext.
#
require 'highline'
require 'syntax'
# scan a ruby source file with Syntax/Ruby. each sequence of tokens that represent a
# string literal is passed to the block, and the value of the block is written to output
# all other tokens are written as-is to output
def replace_strings(source, output, &block)
tokenizer = Syntax.load 'ruby'
current_string = []
# a ruby string literal: :open-quote (:string | :expr) * :close-quote
tokenizer.tokenize(source) do |token|
if token.group == :punct && %w[' "].include?(token)
# FIXME a double or single quote token is always considered string opener or closer
# is this correct?
current_string << token
if current_string.length > 1 && current_string.first == token
# closing a string literal
current_string << token
# pass collected string literal to block, and output the return value
output << yield(current_string)
current_string = []
end
elsif !current_string.empty? &&
( token.group == :string ||
token.group == :expression && token =~ /\A#\{.+\}\Z/ )
# continue string body
current_string << token
else
output << token
end
end
end
filename = ARGV[0]
output = File.new("#{filename}.modified", 'w')
hl = HighLine.new
replace_strings(File.read(filename), output) do |tokens|
hl.say "String literal: #{tokens.join}"
expressions = tokens.select {|t| t.group == :expr}
hl.choose do |menu|
menu.layout = :one_line
menu.default = 'skip'
menu.choice('skip') {tokens.join}
menu.choice('gettextize') do
replacement = '_('
expressions = Hash.new
tokens.each do |t|
if t.group == :expr
expression = /\A#\{(.+)\}\Z/.match(t)[1]
label = hl.ask "Label for expression #{expression}? " do |q|
# if the interpolated code is one word
q.default = expression if expression =~ /\A\w+\Z/
q.validate = /\A\w+\Z/
end
expressions[label] = expression
replacement << "%{#{label}}"
else
replacement << t
end
end
replacement << ')'
replacement << " % {#{
expressions.map {|l, e| ":#{l} => #{e}"}.join ', '
}}" unless expressions.empty?
replacement
end
end
end