From 6ca42947bd2cf63828ba68e96c41d987d5ee6b94 Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 19 Apr 2020 02:45:07 -0500 Subject: [PATCH] search: support quoted values for all metatags. Support using quoted values with all metatags. For example: user:"blah blah", pool:"blah blah", commentary:"blah blah", etc. Things like rating:"safe", id:"42" also work. Both single and double quotes are supported. Also make the status: and rating: metatags fully free. Before only status:deleted and rating:s were free. --- app/logical/post_query_builder.rb | 65 ++++++++++++++++++++-------- config/danbooru_default_config.rb | 4 +- test/unit/post_query_builder_test.rb | 12 +++-- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/app/logical/post_query_builder.rb b/app/logical/post_query_builder.rb index 7ed1bf28e..b8fca8f31 100644 --- a/app/logical/post_query_builder.rb +++ b/app/logical/post_query_builder.rb @@ -1,3 +1,5 @@ +require "strscan" + class PostQueryBuilder COUNT_METATAGS = %w[ comment_count deleted_comment_count active_comment_count @@ -752,16 +754,44 @@ class PostQueryBuilder concerning :ParseMethods do class_methods do - def scan_query(query, strip_metatags: false) - tagstr = query.to_s.gsub(/\u3000/, " ").strip - list = tagstr.scan(/-?(?:source|commentary):".*?"/) || [] - list += tagstr.gsub(/-?(?:source|commentary):".*?"/, "").scan(/[^[:space:]]+/).uniq - list = list.map { |tag| tag.sub(/^[-~]/, "") } if strip_metatags - list + def scan_query(query) + terms = [] + query = query.gsub(/[[:space:]]/, " ") + scanner = StringScanner.new(query) + + until scanner.eos? + scanner.skip(/ +/) + + if scanner.scan(/(#{METATAGS.join("|")}):/i) + metatag = scanner.captures.first + + if scanner.scan(/"(.+)"/) + value = scanner.captures.first + elsif scanner.scan(/'(.+)'/) + value = scanner.captures.first + else + value = scanner.scan(/[^ ]*/) + end + + terms << OpenStruct.new({ type: :metatag, name: metatag.downcase, value: value }) + elsif scanner.scan(/[^ ]+/) + terms << OpenStruct.new({ type: :tag, value: scanner.matched.downcase }) + end + end + + terms end def split_query(query) - scan_query(query) + scan_query(query).map do |term| + if term.type == :metatag && term.value.include?(" ") + "#{term.name}:\"#{term.value}\"" + elsif term.type == :metatag + "#{term.name}:#{term.value}" + elsif term.type == :tag + term.value + end + end end def normalize_query(query, normalize_aliases: true, sort: true) @@ -784,12 +814,13 @@ class PostQueryBuilder :exclude => [] } - scan_query(query).each do |token| - q[:tag_count] += 1 unless Danbooru.config.is_unlimited_tag?(token) + scan_query(query).each do |term| + q[:tag_count] += 1 unless Danbooru.config.is_unlimited_tag?(term) + + if term.type == :metatag + g1 = term.name + g2 = term.value - if token =~ /\A(#{METATAGS.join("|")}):(.+)\z/i - g1 = $1.downcase - g2 = $2 case g1 when "-user" q[:user_neg] ||= [] @@ -896,11 +927,11 @@ class PostQueryBuilder when "-commentary" q[:commentary_neg] ||= [] - q[:commentary_neg] << g2.gsub(/\A"(.*)"\Z/, '\1') + q[:commentary_neg] << g2 when "commentary" q[:commentary] ||= [] - q[:commentary] << g2.gsub(/\A"(.*)"\Z/, '\1') + q[:commentary] << g2 when "search" q[:saved_searches] ||= [] @@ -951,10 +982,10 @@ class PostQueryBuilder q[:filesize] = parse_helper(g2, :filesize) when "source" - q[:source] = g2.gsub(/\A"(.*)"\Z/, '\1') + q[:source] = g2 when "-source" - q[:source_neg] = g2.gsub(/\A"(.*)"\Z/, '\1') + q[:source_neg] = g2 when "date" q[:date] = parse_helper(g2, :date) @@ -1041,7 +1072,7 @@ class PostQueryBuilder end else - parse_tag(token, q[:tags]) + parse_tag(term.value, q[:tags]) end end diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index 1f7a0a1a9..50ea2a6d4 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -108,8 +108,8 @@ module Danbooru end # Return true if the given tag shouldn't count against the user's tag search limit. - def is_unlimited_tag?(tag) - tag.match?(/\A(-?status:deleted|rating:s.*|limit:.+)\z/i) + def is_unlimited_tag?(term) + term.type == :metatag && term.name.in?(%w[status rating limit]) end # After this many pages, the paginator will switch to sequential mode. diff --git a/test/unit/post_query_builder_test.rb b/test/unit/post_query_builder_test.rb index 894efaf0d..9c32fecf1 100644 --- a/test/unit/post_query_builder_test.rb +++ b/test/unit/post_query_builder_test.rb @@ -186,6 +186,8 @@ class PostQueryBuilderTest < ActiveSupport::TestCase assert_tag_match([post1], "pool:TEST_A") assert_tag_match([post2], "pool:Test_B") + assert_tag_match([post2], 'pool:"Test B"') + assert_tag_match([post2], "pool:'Test B'") assert_tag_match([post1], "pool:test_a") assert_tag_match([post2], "-pool:test_a") @@ -481,11 +483,13 @@ class PostQueryBuilderTest < ActiveSupport::TestCase assert_tag_match([post1], "md5:abcd") end - should "return posts for a source search" do - post1 = create(:post, source: "abcd") + should "return posts for a source: search" do + post1 = create(:post, source: "abc def") post2 = create(:post, source: "abcdefg") post3 = create(:post, source: "") + assert_tag_match([post1], 'source:"abc def"') + assert_tag_match([post1], "source:'abc def'") assert_tag_match([post2], "source:abcde") assert_tag_match([post3, post1], "-source:abcde") @@ -748,9 +752,9 @@ class PostQueryBuilderTest < ActiveSupport::TestCase should "not count free tags against the user's search limit" do post1 = create(:post, tag_string: "aaa bbb rating:s") - Danbooru.config.expects(:is_unlimited_tag?).with("rating:s").once.returns(true) - Danbooru.config.expects(:is_unlimited_tag?).with(anything).twice.returns(false) assert_tag_match([post1], "aaa bbb rating:s") + assert_tag_match([post1], "aaa bbb status:active") + assert_tag_match([post1], "aaa bbb limit:20") end should "succeed for exclusive tag searches with no other tag" do