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.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
require "strscan"
|
||||||
|
|
||||||
class PostQueryBuilder
|
class PostQueryBuilder
|
||||||
COUNT_METATAGS = %w[
|
COUNT_METATAGS = %w[
|
||||||
comment_count deleted_comment_count active_comment_count
|
comment_count deleted_comment_count active_comment_count
|
||||||
@@ -752,16 +754,44 @@ class PostQueryBuilder
|
|||||||
|
|
||||||
concerning :ParseMethods do
|
concerning :ParseMethods do
|
||||||
class_methods do
|
class_methods do
|
||||||
def scan_query(query, strip_metatags: false)
|
def scan_query(query)
|
||||||
tagstr = query.to_s.gsub(/\u3000/, " ").strip
|
terms = []
|
||||||
list = tagstr.scan(/-?(?:source|commentary):".*?"/) || []
|
query = query.gsub(/[[:space:]]/, " ")
|
||||||
list += tagstr.gsub(/-?(?:source|commentary):".*?"/, "").scan(/[^[:space:]]+/).uniq
|
scanner = StringScanner.new(query)
|
||||||
list = list.map { |tag| tag.sub(/^[-~]/, "") } if strip_metatags
|
|
||||||
list
|
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
|
end
|
||||||
|
|
||||||
def split_query(query)
|
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
|
end
|
||||||
|
|
||||||
def normalize_query(query, normalize_aliases: true, sort: true)
|
def normalize_query(query, normalize_aliases: true, sort: true)
|
||||||
@@ -784,12 +814,13 @@ class PostQueryBuilder
|
|||||||
:exclude => []
|
:exclude => []
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_query(query).each do |token|
|
scan_query(query).each do |term|
|
||||||
q[:tag_count] += 1 unless Danbooru.config.is_unlimited_tag?(token)
|
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
|
case g1
|
||||||
when "-user"
|
when "-user"
|
||||||
q[:user_neg] ||= []
|
q[:user_neg] ||= []
|
||||||
@@ -896,11 +927,11 @@ class PostQueryBuilder
|
|||||||
|
|
||||||
when "-commentary"
|
when "-commentary"
|
||||||
q[:commentary_neg] ||= []
|
q[:commentary_neg] ||= []
|
||||||
q[:commentary_neg] << g2.gsub(/\A"(.*)"\Z/, '\1')
|
q[:commentary_neg] << g2
|
||||||
|
|
||||||
when "commentary"
|
when "commentary"
|
||||||
q[:commentary] ||= []
|
q[:commentary] ||= []
|
||||||
q[:commentary] << g2.gsub(/\A"(.*)"\Z/, '\1')
|
q[:commentary] << g2
|
||||||
|
|
||||||
when "search"
|
when "search"
|
||||||
q[:saved_searches] ||= []
|
q[:saved_searches] ||= []
|
||||||
@@ -951,10 +982,10 @@ class PostQueryBuilder
|
|||||||
q[:filesize] = parse_helper(g2, :filesize)
|
q[:filesize] = parse_helper(g2, :filesize)
|
||||||
|
|
||||||
when "source"
|
when "source"
|
||||||
q[:source] = g2.gsub(/\A"(.*)"\Z/, '\1')
|
q[:source] = g2
|
||||||
|
|
||||||
when "-source"
|
when "-source"
|
||||||
q[:source_neg] = g2.gsub(/\A"(.*)"\Z/, '\1')
|
q[:source_neg] = g2
|
||||||
|
|
||||||
when "date"
|
when "date"
|
||||||
q[:date] = parse_helper(g2, :date)
|
q[:date] = parse_helper(g2, :date)
|
||||||
@@ -1041,7 +1072,7 @@ class PostQueryBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
parse_tag(token, q[:tags])
|
parse_tag(term.value, q[:tags])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ module Danbooru
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Return true if the given tag shouldn't count against the user's tag search limit.
|
# Return true if the given tag shouldn't count against the user's tag search limit.
|
||||||
def is_unlimited_tag?(tag)
|
def is_unlimited_tag?(term)
|
||||||
tag.match?(/\A(-?status:deleted|rating:s.*|limit:.+)\z/i)
|
term.type == :metatag && term.name.in?(%w[status rating limit])
|
||||||
end
|
end
|
||||||
|
|
||||||
# After this many pages, the paginator will switch to sequential mode.
|
# After this many pages, the paginator will switch to sequential mode.
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
assert_tag_match([post1], "pool:TEST_A")
|
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([post2], "pool:'Test B'")
|
||||||
|
|
||||||
assert_tag_match([post1], "pool:test_a")
|
assert_tag_match([post1], "pool:test_a")
|
||||||
assert_tag_match([post2], "-pool:test_a")
|
assert_tag_match([post2], "-pool:test_a")
|
||||||
@@ -481,11 +483,13 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
|||||||
assert_tag_match([post1], "md5:abcd")
|
assert_tag_match([post1], "md5:abcd")
|
||||||
end
|
end
|
||||||
|
|
||||||
should "return posts for a source search" do
|
should "return posts for a source:<text> search" do
|
||||||
post1 = create(:post, source: "abcd")
|
post1 = create(:post, source: "abc def")
|
||||||
post2 = create(:post, source: "abcdefg")
|
post2 = create(:post, source: "abcdefg")
|
||||||
post3 = create(:post, source: "")
|
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([post2], "source:abcde")
|
||||||
assert_tag_match([post3, post1], "-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
|
should "not count free tags against the user's search limit" do
|
||||||
post1 = create(:post, tag_string: "aaa bbb rating:s")
|
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 rating:s")
|
||||||
|
assert_tag_match([post1], "aaa bbb status:active")
|
||||||
|
assert_tag_match([post1], "aaa bbb limit:20")
|
||||||
end
|
end
|
||||||
|
|
||||||
should "succeed for exclusive tag searches with no other tag" do
|
should "succeed for exclusive tag searches with no other tag" do
|
||||||
|
|||||||
Reference in New Issue
Block a user