Merge pull request from sebastianludwig/cli_refactoring

CLI refactoring
This commit is contained in:
Sebastian Celis 2015-11-29 11:34:02 -06:00
commit 92e869b065
8 changed files with 374 additions and 129 deletions

View file

@ -1,17 +1,19 @@
require 'optparse'
module Twine
class CLI
def initialize(args, options)
@options = options
@args = args
end
module CLI
NEEDED_COMMAND_ARGUMENTS = {
'generate-string-file' => 3,
'generate-all-string-files' => 3,
'consume-string-file' => 3,
'consume-all-string-files' => 3,
'generate-loc-drop' => 3,
'consume-loc-drop' => 3,
'validate-strings-file' => 2
}
def self.parse_args(args, options)
new(args, options).parse_args
end
def parse_args
def self.parse(args)
options = {}
parser = OptionParser.new do |opts|
opts.banner = 'Usage: twine COMMAND STRINGS_FILE [INPUT_OR_OUTPUT_PATH] [--lang LANG1,LANG2...] [--tags TAG1,TAG2,TAG3...] [--format FORMAT]'
opts.separator ''
@ -36,61 +38,56 @@ module Twine
opts.separator 'General Options:'
opts.separator ''
opts.on('-l', '--lang LANGUAGES', Array, 'The language code(s) to use for the specified action.') do |langs|
@options[:languages] = langs
options[:languages] = langs
end
opts.on('-t', '--tags TAGS', Array, 'The tag(s) to use for the specified action. Only strings with that tag will be processed. Do not specify any tags to match all strings in the strings data file.') do |tags|
@options[:tags] = tags
options[:tags] = tags
end
opts.on('-u', '--untagged', 'If you have specified tags using the --tags flag, then only those tags will be selected. If you also want to select all strings that are untagged, then you can specify this option to do so.') do |u|
@options[:untagged] = true
end
formats = []
Formatters.formatters.each do |formatter|
formats << formatter::FORMAT_NAME
options[:untagged] = true
end
formats = Formatters.formatters.map { |f| f::FORMAT_NAME }
opts.on('-f', '--format FORMAT', "The file format to read or write (#{formats.join(', ')}). Additional formatters can be placed in the formats/ directory.") do |format|
lformat = format.downcase
if !formats.include?(lformat)
unless formats.include?(format.downcase)
raise Twine::Error.new "Invalid format: #{format}"
end
@options[:format] = lformat
options[:format] = format.downcase
end
opts.on('-a', '--consume-all', 'Normally, when consuming a string file, Twine will ignore any string keys that do not exist in your master file.') do |a|
@options[:consume_all] = true
options[:consume_all] = true
end
opts.on('-i', '--include SET', "This flag will determine which strings are included when generating strings files. It's possible values:",
" all: All strings both translated and untranslated for the specified language are included. This is the default value.",
" translated: Only translated strings are included.",
" untranslated: Only untranslated strings are included.") do |set|
set = set.downcase
unless ['all', 'translated', 'untranslated'].include?(set)
unless ['all', 'translated', 'untranslated'].include?(set.downcase)
raise Twine::Error.new "Invalid include flag: #{set}"
end
@options[:include] = set
options[:include] = set.downcase
end
unless @options[:include]
@options[:include] = 'all'
unless options[:include]
options[:include] = 'all'
end
opts.on('-o', '--output-file OUTPUT_FILE', 'Write the new strings database to this file instead of replacing the original file. This flag is only useful when running the consume-string-file or consume-loc-drop commands.') do |o|
@options[:output_path] = o
options[:output_path] = o
end
opts.on('-n', '--file-name FILE_NAME', 'When running the generate-all-string-files command, this flag may be used to overwrite the default file name of the format.') do |n|
@options[:file_name] = n
options[:file_name] = n
end
opts.on('-r', '--create-folders', "When running the generate-all-string-files command, this flag may be used to create output folders for all languages, if they don't exist yet. As a result all languages will be exported, not only the ones where an output folder already exists.") do |r|
@options[:create_folders] = true
options[:create_folders] = true
end
opts.on('-d', '--developer-language LANG', 'When writing the strings data file, set the specified language as the "developer language". In practice, this just means that this language will appear first in the strings data file. When generating files this language will be used as default language and its translations will be used if a key is not localized for the output language.') do |d|
@options[:developer_language] = d
options[:developer_language] = d
end
opts.on('-c', '--consume-comments', 'Normally, when consuming a string file, Twine will ignore all comments in the file. With this flag set, any comments encountered will be read and parsed into the strings data file. This is especially useful when creating your first strings data file from an existing project.') do |c|
@options[:consume_comments] = true
options[:consume_comments] = true
end
opts.on('-e', '--encoding ENCODING', 'Twine defaults to encoding all output files in UTF-8. This flag will tell Twine to use an alternate encoding for these files. For example, you could use this to write Apple .strings files in UTF-16. This flag is currently only supported in Ruby 1.9.3 or greater.') do |e|
if !"".respond_to?(:encode)
unless "".respond_to? :encode
raise Twine::Error.new "The --encoding flag is only supported on Ruby 1.9.3 or greater."
end
@options[:output_encoding] = e
options[:output_encoding] = e
end
opts.on('-h', '--help', 'Show this message.') do |h|
puts opts.help
@ -111,88 +108,56 @@ module Twine
opts.separator '> twine consume-loc-drop strings.txt LocDrop5.zip'
opts.separator '> twine validate-strings-file strings.txt'
end
parser.parse! @args
parser.parse! args
if @args.length == 0
if args.length == 0
puts parser.help
exit
end
@options[:command] = @args[0]
if !VALID_COMMANDS.include? @options[:command]
raise Twine::Error.new "Invalid command: #{@options[:command]}"
number_of_needed_arguments = NEEDED_COMMAND_ARGUMENTS[args[0]]
unless number_of_needed_arguments
raise Twine::Error.new "Invalid command: #{args[0]}"
end
options[:command] = args[0]
if @args.length == 1
if args.length < 2
raise Twine::Error.new 'You must specify your strings file.'
end
options[:strings_file] = args[1]
@options[:strings_file] = @args[1]
if args.length < number_of_needed_arguments
raise Twine::Error.new 'Not enough arguments.'
elsif args.length > number_of_needed_arguments
raise Twine::Error.new "Unknown argument: #{args[number_of_needed_arguments]}"
end
case @options[:command]
case options[:command]
when 'generate-string-file'
if @args.length == 3
@options[:output_path] = @args[2]
elsif @args.length > 3
raise Twine::Error.new "Unknown argument: #{@args[3]}"
else
raise Twine::Error.new 'Not enough arguments.'
end
if @options[:languages] and @options[:languages].length > 1
options[:output_path] = args[2]
if options[:languages] and options[:languages].length > 1
raise Twine::Error.new 'Please only specify a single language for the generate-string-file command.'
end
when 'generate-all-string-files'
if @args.length == 3
@options[:output_path] = @args[2]
elsif @args.length > 3
raise Twine::Error.new "Unknown argument: #{@args[3]}"
else
raise Twine::Error.new 'Not enough arguments.'
end
options[:output_path] = args[2]
when 'consume-string-file'
if @args.length == 3
@options[:input_path] = @args[2]
elsif @args.length > 3
raise Twine::Error.new "Unknown argument: #{@args[3]}"
else
raise Twine::Error.new 'Not enough arguments.'
end
if @options[:languages] and @options[:languages].length > 1
options[:input_path] = args[2]
if options[:languages] and options[:languages].length > 1
raise Twine::Error.new 'Please only specify a single language for the consume-string-file command.'
end
when 'consume-all-string-files'
if @args.length == 3
@options[:input_path] = @args[2]
elsif @args.length > 3
raise Twine::Error.new "Unknown argument: #{@args[3]}"
else
raise Twine::Error.new 'Not enough arguments.'
end
options[:input_path] = args[2]
when 'generate-loc-drop'
if @args.length == 3
@options[:output_path] = @args[2]
elsif @args.length > 3
raise Twine::Error.new "Unknown argument: #{@args[3]}"
else
raise Twine::Error.new 'Not enough arguments.'
end
if !@options[:format]
options[:output_path] = args[2]
if !options[:format]
raise Twine::Error.new 'You must specify a format.'
end
when 'consume-loc-drop'
if @args.length == 3
@options[:input_path] = @args[2]
elsif @args.length > 3
raise Twine::Error.new "Unknown argument: #{@args[3]}"
else
raise Twine::Error.new 'Not enough arguments.'
end
options[:input_path] = args[2]
when 'validate-strings-file'
if @args.length > 2
raise Twine::Error.new "Unknown argument: #{@args[2]}"
end
end
return options
end
end
end

View file

@ -4,26 +4,37 @@ require 'fileutils'
Twine::Plugin.new # Initialize plugins first in Runner.
module Twine
VALID_COMMANDS = ['generate-string-file', 'generate-all-string-files', 'consume-string-file', 'consume-all-string-files', 'generate-loc-drop', 'consume-loc-drop', 'validate-strings-file']
class Runner
def initialize(args, options = {}, strings = StringsFile.new)
@args = args
def self.run(args)
options = CLI.parse(args)
strings = StringsFile.new
strings.read options[:strings_file]
runner = new(options, strings)
case options[:command]
when 'generate-string-file'
runner.generate_string_file
when 'generate-all-string-files'
runner.generate_all_string_files
when 'consume-string-file'
runner.consume_string_file
when 'consume-all-string-files'
runner.consume_all_string_files
when 'generate-loc-drop'
runner.generate_loc_drop
when 'consume-loc-drop'
runner.consume_loc_drop
when 'validate-strings-file'
runner.validate_strings_file
end
end
def initialize(options = {}, strings = StringsFile.new)
@options = options
@strings = strings
end
def self.run(args)
new(args).run
end
def run
# Parse all CLI arguments.
CLI::parse_args(@args, @options)
@strings.read @options[:strings_file]
execute_command
end
def write_strings_data(path)
if @options[:developer_language]
@strings.set_developer_language_code(@options[:developer_language])
@ -31,25 +42,6 @@ module Twine
@strings.write(path)
end
def execute_command
case @options[:command]
when 'generate-string-file'
generate_string_file
when 'generate-all-string-files'
generate_all_string_files
when 'consume-string-file'
consume_string_file
when 'consume-all-string-files'
consume_all_string_files
when 'generate-loc-drop'
generate_loc_drop
when 'consume-loc-drop'
consume_loc_drop
when 'validate-strings-file'
validate_strings_file
end
end
def generate_string_file
lang = nil
if @options[:languages]

View file

@ -1,8 +1,6 @@
require 'twine_test_case'
class CommandTestCase < TwineTestCase
KNOWN_LANGUAGES = %w(en fr de es)
def prepare_mock_formatter(formatter_class)
strings = Twine::StringsFile.new
strings.language_codes.concat KNOWN_LANGUAGES

288
test/test_cli.rb Normal file
View file

@ -0,0 +1,288 @@
require 'twine_test_case'
class CLITestCase < TwineTestCase
def setup
super
@strings_file_path = File.join @output_dir, SecureRandom.uuid
@input_path = File.join @output_dir, SecureRandom.uuid
@input_dir = @output_dir
end
def parse(command)
@options = Twine::CLI::parse command.split
end
class TestValidateStringsFile < CLITestCase
def test_command
parse "validate-strings-file #{@strings_file_path}"
assert_equal 'validate-strings-file', @options[:command]
assert_equal @strings_file_path, @options[:strings_file]
end
def test_missing_parameter
assert_raises Twine::Error do
parse 'validate-strings-file'
end
end
def test_extra_parameter
assert_raises Twine::Error do
parse 'validate-strings-file strings extra'
end
end
end
class TestGenerateStringFile < CLITestCase
def test_command
parse "generate-string-file #{@strings_file_path} #{@output_path}"
assert_equal 'generate-string-file', @options[:command]
assert_equal @strings_file_path, @options[:strings_file]
assert_equal @output_path, @options[:output_path]
end
def test_missing_parameter
assert_raises Twine::Error do
parse 'generate-string-file strings'
end
end
def test_extra_parameter
assert_raises Twine::Error do
parse 'generate-string-file strings output extra'
end
end
def test_only_allows_one_language
assert_raises Twine::Error do
parse "generate-string-file strings output --lang en,fr"
end
end
end
class TestGenerateAllStringFiles < CLITestCase
def test_command
parse "generate-all-string-files #{@strings_file_path} #{@output_dir}"
assert_equal 'generate-all-string-files', @options[:command]
assert_equal @strings_file_path, @options[:strings_file]
assert_equal @output_dir, @options[:output_path]
end
def test_missing_parameter
assert_raises Twine::Error do
parse "generate-all-string-files strings"
end
end
def test_extra_parameter
assert_raises Twine::Error do
parse "generate-all-string-files strings output extra"
end
end
end
class TestConsumeStringFile < CLITestCase
def test_command
parse "consume-string-file #{@strings_file_path} #{@input_path}"
assert_equal 'consume-string-file', @options[:command]
assert_equal @strings_file_path, @options[:strings_file]
assert_equal @input_path, @options[:input_path]
end
def test_missing_parameter
assert_raises Twine::Error do
parse "consume-string-file strings"
end
end
def test_extra_parameter
assert_raises Twine::Error do
parse "consume-string-file strings output extra"
end
end
def test_only_allows_one_language
assert_raises Twine::Error do
parse "consume-string-file strings output --lang en,fr"
end
end
end
class TestConsumeAllStringFiles < CLITestCase
def test_command
parse "consume-all-string-files #{@strings_file_path} #{@input_dir}"
assert_equal 'consume-all-string-files', @options[:command]
assert_equal @strings_file_path, @options[:strings_file]
assert_equal @input_dir, @options[:input_path]
end
def test_missing_parameter
assert_raises Twine::Error do
parse "consume-all-string-files strings"
end
end
def test_extra_parameter
assert_raises Twine::Error do
parse "consume-all-string-files strings output extra"
end
end
end
class TestGenerateLocDrop < CLITestCase
def test_command
parse "generate-loc-drop #{@strings_file_path} #{@output_path} --format apple"
assert_equal 'generate-loc-drop', @options[:command]
assert_equal @strings_file_path, @options[:strings_file]
assert_equal @output_path, @options[:output_path]
end
def test_missing_parameter
assert_raises Twine::Error do
parse "generate-loc-drop strings --format apple"
end
end
def test_extra_parameter
assert_raises Twine::Error do
parse "generate-loc-drop strings output extra --format apple"
end
end
def test_format_needed
assert_raises Twine::Error do
parse "generate-loc-drop strings output"
end
end
end
class TestConsumeLocDrop < CLITestCase
def test_command
parse "consume-loc-drop #{@strings_file_path} #{@input_path}"
assert_equal 'consume-loc-drop', @options[:command]
assert_equal @strings_file_path, @options[:strings_file]
assert_equal @input_path, @options[:input_path]
end
def test_missing_parameter
assert_raises Twine::Error do
parse "consume-loc-drop strings"
end
end
def test_extra_parameter
assert_raises Twine::Error do
parse "consume-loc-drop strings input extra"
end
end
end
class TestParameters < CLITestCase
def parse_with(parameter)
parse 'validate-strings-file input.txt ' + parameter
end
def test_default_options
parse_with ''
expected = {command: 'validate-strings-file', strings_file: 'input.txt', include: "all"}
assert_equal expected, @options
end
def test_create_folders
parse_with '--create-folders'
assert @options[:create_folders]
end
def test_consume_all
parse_with '--consume-all'
assert @options[:consume_all]
end
def test_consume_comments
parse_with '--consume-comments'
assert @options[:consume_comments]
end
def test_untagged
parse_with '--untagged'
assert @options[:untagged]
end
def test_developer_language
random_language = KNOWN_LANGUAGES.sample
parse_with "--developer-lang #{random_language}"
assert_equal random_language, @options[:developer_language]
end
def test_single_language
random_language = KNOWN_LANGUAGES.sample
parse_with "--lang #{random_language}"
assert_equal [random_language], @options[:languages]
end
def test_multiple_languages
random_languages = KNOWN_LANGUAGES.shuffle[0, 3]
parse_with "--lang #{random_languages.join(',')}"
assert_equal random_languages.sort, @options[:languages].sort
end
def test_single_tag
random_tag = "tag#{rand(100)}"
parse_with "--tags #{random_tag}"
assert_equal [random_tag], @options[:tags]
end
def test_multiple_tags
random_tags = ([nil] * 3).map { "tag#{rand(100)}" }
parse_with "--tags #{random_tags.join(',')}"
assert_equal random_tags.sort, @options[:tags].sort
end
def test_format
random_format = Twine::Formatters.formatters.sample::FORMAT_NAME
parse_with "--format #{random_format}"
assert_equal random_format, @options[:format]
end
def test_format_ignores_case
random_format = Twine::Formatters.formatters.sample::FORMAT_NAME
parse_with "--format #{random_format.upcase}"
assert_equal random_format, @options[:format]
end
def test_include
random_set = ['all', 'translated', 'untranslated'].sample
parse_with "--include #{random_set}"
assert_equal random_set, @options[:include]
end
def test_include_ignores_case
random_set = ['all', 'translated', 'untranslated'].sample
parse_with "--include #{random_set.upcase}"
assert_equal random_set, @options[:include]
end
def test_output_path
parse_with "--output-file #{@output_path}"
assert_equal @output_path, @options[:output_path]
end
def test_file_name
random_filename = "#{rand(10000)}"
parse_with "--file-name #{random_filename}"
assert_equal random_filename, @options[:file_name]
end
def test_encoding
parse_with '--encoding UTF16'
assert_equal 'UTF16', @options[:output_encoding]
end
end
end

View file

@ -11,7 +11,7 @@ class TestConsumeStringFile < CommandTestCase
@strings = Twine::StringsFile.new
@strings.language_codes.concat KNOWN_LANGUAGES
Twine::Runner.new(nil, options, @strings)
Twine::Runner.new(options, @strings)
end
def prepare_mock_read_file_formatter(formatter_class)

View file

@ -14,7 +14,7 @@ class TestGenerateAllStringFiles < CommandTestCase
end
end
Twine::Runner.new(nil, options, @twine_file)
Twine::Runner.new(options, @twine_file)
end
def test_fails_if_output_folder_does_not_exist

View file

@ -9,7 +9,7 @@ class TestGenerateStringFile < CommandTestCase
@strings = Twine::StringsFile.new
@strings.language_codes.concat KNOWN_LANGUAGES
Twine::Runner.new(nil, options, @strings)
Twine::Runner.new(options, @strings)
end
def prepare_mock_write_file_formatter(formatter_class)

View file

@ -7,6 +7,8 @@ require 'twine_file_dsl'
class TwineTestCase < Minitest::Test
include TwineFileDSL
KNOWN_LANGUAGES = %w(en fr de es)
def setup
super