Support multiple collections in path parsing

This commit is contained in:
James Smith committed 2026-04-29 09:44:48 +01:00
1 parent f1c013b96f
commit d4d2cf0611
6 files changed
+40 -21

No files matched your search

+10 -6
View File
@@ -27,7 +27,7 @@ class Components::PathTemplatePreview < Components::Base
tr do
td(style: "word-break: break-all") { path }
td { find_or_new_from_path_component(Creator, parsed[:creator])&.name || "❌" }
td { find_or_new_from_path_component(Collection, parsed[:collection])&.name || "❌" }
td { parsed[:collections] ? Array(find_or_new_from_path_component(Collection, parsed[:collections])).map(&:name).join(", ") : "❌" }
td {
parsed[:tags]&.map do |it|
Tag tag: ActsAsTaggableOn::Tag.new(name: it), link: false
@@ -46,11 +46,15 @@ class Components::PathTemplatePreview < Components::Base
def find_or_new_from_path_component(klass, path_component)
return unless path_component
klass.find_by(slug: path_component) ||
klass.find_by(
name: to_human_name(path_component)
) ||
klass.new(name: to_human_name(path_component))
if path_component.is_a? Array
path_component.map { |it| find_or_new_from_path_component(klass, it) }
else
klass.find_by(slug: path_component) ||
klass.find_by(
name: to_human_name(path_component)
) ||
klass.new(name: to_human_name(path_component))
end
end
def to_human_name(str)
+9 -5
View File
@@ -90,7 +90,7 @@ class Scan::Model::ParseMetadataJob < ApplicationJob
components = PathParserService.new(library.path_template, path).call
{
creator: find_or_create_from_path_component(Creator, components[:creator]),
collections: [find_or_create_from_path_component(Collection, components[:collection])].compact,
collections: Array(find_or_create_from_path_component(Collection, components[:collections])),
name: to_human_name(components[:model_name])
}.compact_blank
end
@@ -170,10 +170,14 @@ class Scan::Model::ParseMetadataJob < ApplicationJob
def find_or_create_from_path_component(klass, path_component)
return unless path_component
klass.find_by(slug: path_component) ||
klass.create_with(slug: path_component.parameterize).find_or_create_by(
name: to_human_name(path_component)
)
if path_component.is_a? Array
path_component.map { |it| find_or_create_from_path_component(klass, it) }
else
klass.find_by(slug: path_component) ||
klass.create_with(slug: path_component.parameterize).find_or_create_by(
name: to_human_name(path_component)
)
end
end
def to_human_name(str)
+9 -6
View File
@@ -8,9 +8,10 @@ class PathParserService
components = @path.match(path_parse_pattern)&.named_captures&.symbolize_keys
return {} if components.nil?
components.merge({
tags: components[:tags]&.split("/")&.compact_blank,
tags: components[:tags]&.split(File::SEPARATOR)&.compact_blank,
collections: components[:collection] ? [components.delete(:collection)] : components[:collections]&.split("/")&.compact_blank,
model_id: nil # discard ID, never gonna use it in parsing
}).compact
}).compact_blank
end
private
@@ -22,15 +23,17 @@ class PathParserService
when "{tags}"
"(?<tags>[[:print:]]*)"
when "{creator}"
"(?<creator>[[:print:]&&[^/]]*?)"
"(?<creator>[[:print:]&&[^#{File::SEPARATOR}]]*?)"
when "{collection}"
"(?<collection>[[:print:]&&[^/]]*?)"
"(?<collection>[[:print:]&&[^#{File::SEPARATOR}]]*?)"
when "{collections}"
"(?<collections>[[:print:]]*)"
when "{modelName}"
"(?<model_name>[[:print:]&&[^/]]*?)"
"(?<model_name>[[:print:]&&[^#{File::SEPARATOR}]]*?)"
when "{modelId}"
"(?<model_id>#[[:digit:]]+)?"
else
"[[:print:]&&[^/]]*"
"[[:print:]&&[^#{File::SEPARATOR}]]*"
end
} + "$")
end
@@ -3,6 +3,7 @@ class PathTemplateValidator < ActiveModel::EachValidator
"tags",
"creator",
"collection",
"collections",
"modelName",
"modelId"
]
@@ -9,7 +9,7 @@
<code>{creator}</code>: <%= t ".tokens.creator" %>
</li>
<li>
<code>{collection}</code>: <%= t ".tokens.collection" %>
<code>{collections}</code>: <%= t ".tokens.collections" %>
</li>
<li>
<code>{modelName}</code>: <%= t ".tokens.model_name" %>
+10 -3
View File
@@ -7,10 +7,12 @@ RSpec.describe PathParserService do
"{tags}" => %r{^/?.*?(?<tags>[[:print:]]*)$},
"{creator}" => %r{^/?.*?(?<creator>[[:print:]&&[^/]]*?)$},
"{collection}" => %r{^/?.*?(?<collection>[[:print:]&&[^/]]*?)$},
"{collections}" => %r{^/?.*?(?<collections>[[:print:]]*)$},
"{tags}/{creator}" => %r{^/?.*?(?<tags>[[:print:]]*)/(?<creator>[[:print:]&&[^/]]*?)$},
"{tags}/{creator}/{modelName}{modelId}" => %r{^/?.*?(?<tags>[[:print:]]*)/(?<creator>[[:print:]&&[^/]]*?)/(?<model_name>[[:print:]&&[^/]]*?)(?<model_id>#[[:digit:]]+)?$},
"@{creator}{modelId}" => %r{^/?.*?@(?<creator>[[:print:]&&[^/]]*?)(?<model_id>#[[:digit:]]+)?$},
"{creator}/{collection}/{tags}/{modelName}{modelId}" => %r{^/?.*?(?<creator>[[:print:]&&[^/]]*?)/(?<collection>[[:print:]&&[^/]]*?)/(?<tags>[[:print:]]*)/(?<model_name>[[:print:]&&[^/]]*?)(?<model_id>#[[:digit:]]+)?$}
"{creator}/{collection}/{tags}/{modelName}{modelId}" => %r{^/?.*?(?<creator>[[:print:]&&[^/]]*?)/(?<collection>[[:print:]&&[^/]]*?)/(?<tags>[[:print:]]*)/(?<model_name>[[:print:]&&[^/]]*?)(?<model_id>#[[:digit:]]+)?$},
"{collections}/{creator}" => %r{^/?.*?(?<collections>[[:print:]]*)/(?<creator>[[:print:]&&[^/]]*?)$}
}.each_pair do |tag, regexp|
it "correctly converts #{tag} into a regexp matcher" do
service = described_class.new(tag, path)
@@ -28,7 +30,7 @@ RSpec.describe PathParserService do
model_name: "prefix - name"
},
"{collection}/{modelName}{modelId}" => {
collection: "bottom",
collections: ["bottom"],
model_name: "prefix - name"
},
"{tags}/{creator}/{modelName}{modelId}" => {
@@ -42,8 +44,13 @@ RSpec.describe PathParserService do
"{tags}/{creator}/{collection} - {modelName}{modelId}" => {
tags: ["top", "middle"],
creator: "bottom",
collection: "prefix",
collections: ["prefix"],
model_name: "name"
},
"{collections}/{creator}/{modelName}{modelId}" => {
collections: ["top", "middle"],
creator: "bottom",
model_name: "prefix - name"
}
}.each_pair do |tag, values|
it "correctly matches components of #{tag}" do