Added AND and NOT logic for tags.

This commit is contained in:
Sebastian Ludwig 2016-06-18 18:35:12 +02:00
parent 762196050e
commit 7c84dbb418
6 changed files with 128 additions and 46 deletions

View file

@ -35,6 +35,8 @@ Twine supports [`printf` style placeholders][printf] with one peculiarity: `@` i
Tags are used by Twine as a way to only work with a subset of your definitions at any given point in time. Each definition can be assigned zero or more tags which are separated by commas. Tags are optional, though highly recommended. You can get a list of all definitions currently missing tags by executing the [`validate-twine-file`](#validate-twine-file) command with the `--pedantic` option.
When generating a localization file, you can specify which definitions should be included using the `--tags` option. Provide a comma separated list of tags to match all definitions that contain any of the tags (`--tags tag1,tag2` matches all definitions tagged with `tag1` _or_ `tag2`). Provide multiple `--tags` options to match defintions containing all specified tags (`--tags tag1 --tags tag2` matches all definitions tagged with `tag1` _and_ `tag2`). You can match definitions _not_ containing a tag by prefixing the tag with a tilde (`--tags ~tag1` matches all definitions _not_ tagged with `tag1`.). All three options are combinable.
### Whitespace
Whitepace in this file is mostly ignored. If you absolutely need to put spaces at the beginning or end of your translated string, you can wrap the entire string in a pair of `` ` `` characters. If your actual string needs to start *and* end with a grave accent, you can wrap it in another pair of `` ` `` characters. See the example, below.

View file

@ -48,9 +48,11 @@ module Twine
opts.on('-l', '--lang LANGUAGES', Array, 'The language code(s) to use for the specified action.') do |l|
options[:languages] = l
end
opts.on('-t', '--tags TAG1,TAG2,TAG3', Array, 'The tag(s) to use for the specified action. Only definitions with that tag will be processed. Omit this option to match',
' all definitions in the Twine data file.') do |t|
options[:tags] = t
opts.on('-t', '--tags TAG1,TAG2,TAG3', Array, 'The tag(s) to use for the specified action. Only definitions with ANY of the specified tags will be processed.',
' Specify this option multiple times to only include definitions with ALL of the specified tags. Prefix a tag',
' with ~ to include definitions NOT containing that tag. Omit this option to match all definitions in the Twine',
' data file.') do |t|
options[:tags] = (options[:tags] || []) << t
end
opts.on('-u', '--[no-]untagged', 'If you have specified tags using the --tags flag, then only those tags will be selected. If you also want to select',
' all definitions that are untagged, then you can specify this option to do so.') do |u|
@ -75,7 +77,8 @@ module Twine
' running the consume-localization-file or consume-loc-drop commands.') do |o|
options[:output_path] = o
end
opts.on('-n', '--file-name FILE_NAME', 'When running the generate-all-localization-files command, this flag may be used to overwrite the default file name of the format.') do |n|
opts.on('-n', '--file-name FILE_NAME', 'When running the generate-all-localization-files command, this flag may be used to overwrite the default file name of',
' the format.') do |n|
options[:file_name] = n
end
opts.on('-r', '--[no-]create-folders', "When running the generate-all-localization-files command, this flag may be used to create output folders for all languages,",

View file

@ -22,17 +22,22 @@ module Twine
@comment
end
# [['tag1', 'tag2'], ['~tag3']] == (tag1 OR tag2) AND (!tag3)
def matches_tags?(tags, include_untagged)
if tags == nil || tags.empty?
# The user did not specify any tags. Everything passes.
if tags == nil || tags.empty? # The user did not specify any tags. Everything passes.
return true
elsif @tags == nil
# This definition has no tags.
elsif @tags == nil # This definition has no tags -> check reference (if any)
return reference ? reference.matches_tags?(tags, include_untagged) : include_untagged
elsif @tags.empty?
return include_untagged
else
return !(tags & @tags).empty?
return tags.all? do |set|
regular_tags, negated_tags = set.partition { |tag| tag[0] != '~' }
negated_tags.map! { |tag| tag[1..-1] }
matches_regular_tags = (!regular_tags.empty? && !(regular_tags & @tags).empty?)
matches_negated_tags = (!negated_tags.empty? && (negated_tags & @tags).empty?)
matches_regular_tags or matches_negated_tags
end
end
return false

View file

@ -256,13 +256,21 @@ class CLITest < TwineTest
def test_single_tag
random_tag = "tag#{rand(100)}"
parse_with "--tags #{random_tag}"
assert_equal [random_tag], @options[:tags]
assert_equal [[random_tag]], @options[:tags]
end
def test_multiple_tags
random_tags = ([nil] * 3).map { "tag#{rand(100)}" }
def test_multiple_OR_tags
random_tags = ["tag#{rand(100)}", "tag#{rand(100)}", "tag#{rand(100)}"]
parse_with "--tags #{random_tags.join(',')}"
assert_equal random_tags.sort, @options[:tags].sort
sorted_tags = @options[:tags].map { |tags| tags.sort }
assert_equal [random_tags.sort], sorted_tags
end
def test_multiple_AND_tags
random_tag_1 = "tag#{rand(100)}"
random_tag_2 = "tag#{rand(100)}"
parse_with "--tags #{random_tag_1} --tags #{random_tag_2}"
assert_equal [[random_tag_1], [random_tag_2]], @options[:tags]
end
def test_format

View file

@ -22,21 +22,21 @@ class TestOutputProcessor < TwineTest
end
def test_filter_by_tag
processor = Twine::Processors::OutputProcessor.new(@twine_file, { tags: ['tag1'] })
processor = Twine::Processors::OutputProcessor.new(@twine_file, { tags: [['tag1']] })
result = processor.process('en')
assert_equal %w(key1 key2), result.definitions_by_key.keys.sort
end
def test_filter_by_multiple_tags
processor = Twine::Processors::OutputProcessor.new(@twine_file, { tags: ['tag1', 'tag2'] })
processor = Twine::Processors::OutputProcessor.new(@twine_file, { tags: [['tag1', 'tag2']] })
result = processor.process('en')
assert_equal %w(key1 key2 key3), result.definitions_by_key.keys.sort
end
def test_filter_untagged
processor = Twine::Processors::OutputProcessor.new(@twine_file, { tags: ['tag1'], untagged: true })
processor = Twine::Processors::OutputProcessor.new(@twine_file, { tags: [['tag1']], untagged: true })
result = processor.process('en')
assert_equal %w(key1 key2 key4), result.definitions_by_key.keys.sort

View file

@ -1,47 +1,111 @@
require 'twine_test'
class TestTwineDefinition < TwineTest
def setup
super
class TestTags < TwineTest
def setup
super
@definition = Twine::TwineDefinition.new 'key'
end
@reference = Twine::TwineDefinition.new 'reference-key'
@reference.comment = 'reference comment'
@reference.tags = ['ref1']
@reference.translations['en'] = 'ref-value'
def test_include_untagged
assert @definition.matches_tags?([[rand(100000).to_s]], true)
end
@definition = Twine::TwineDefinition.new 'key'
@definition.reference_key = @reference.key
@definition.reference = @reference
def test_matches_no_given_tags
assert @definition.matches_tags?([], false)
end
def test_matches_tag
@definition.tags = ['tag1']
assert @definition.matches_tags?([['tag1']], false)
end
def test_matches_any_tag
@definition.tags = ['tag1']
assert @definition.matches_tags?([['tag0', 'tag1', 'tag2']], false)
end
def test_matches_all_tags
@definition.tags = ['tag1', 'tag2']
assert @definition.matches_tags?([['tag1'], ['tag2']], false)
end
def test_does_not_match_all_tags
@definition.tags = ['tag1']
refute @definition.matches_tags?([['tag1'], ['tag2']], false)
end
def test_does_not_match_excluded_tag
@definition.tags = ['tag1']
refute @definition.matches_tags?([['~tag1']], false)
end
def test_matches_excluded_tag
@definition.tags = ['tag2']
assert @definition.matches_tags?([['~tag1']], false)
end
def test_complex_rules
@definition.tags = ['tag1', 'tag2', 'tag3']
assert @definition.matches_tags?([['tag1']], false)
assert @definition.matches_tags?([['tag1', 'tag4']], false)
assert @definition.matches_tags?([['tag1'], ['tag2'], ['tag3']], false)
refute @definition.matches_tags?([['tag1'], ['tag4']], false)
assert @definition.matches_tags?([['tag4', '~tag5']], false)
end
end
def test_reference_comment_used
assert_equal 'reference comment', @definition.comment
end
class TestReferences < TwineTest
def setup
super
def test_reference_comment_override
@definition.comment = 'definition comment'
@reference = Twine::TwineDefinition.new 'reference-key'
@reference.comment = 'reference comment'
@reference.tags = ['ref1']
@reference.translations['en'] = 'ref-value'
assert_equal 'definition comment', @definition.comment
end
@definition = Twine::TwineDefinition.new 'key'
@definition.reference_key = @reference.key
@definition.reference = @reference
end
def test_reference_tags_used
assert @definition.matches_tags?(['ref1'], false)
end
def test_reference_comment_used
assert_equal 'reference comment', @definition.comment
end
def test_reference_tags_override
@definition.tags = ['tag1']
def test_reference_comment_override
@definition.comment = 'definition comment'
refute @definition.matches_tags?(['ref1'], false)
assert @definition.matches_tags?(['tag1'], false)
end
assert_equal 'definition comment', @definition.comment
end
def test_reference_translation_used
assert_equal 'ref-value', @definition.translation_for_lang('en')
end
def test_reference_tags_used
assert @definition.matches_tags?([['ref1']], false)
end
def test_reference_translation_override
@definition.translations['en'] = 'value'
def test_reference_tags_override
@definition.tags = ['tag1']
assert_equal 'value', @definition.translation_for_lang('en')
refute @definition.matches_tags?([['ref1']], false)
assert @definition.matches_tags?([['tag1']], false)
end
def test_reference_translation_used
assert_equal 'ref-value', @definition.translation_for_lang('en')
end
def test_reference_translation_override
@definition.translations['en'] = 'value'
assert_equal 'value', @definition.translation_for_lang('en')
end
end
end