Skip to content

Commit 52bf674

Browse files
authored
Merge pull request #1079 from Floppy/change-path-parsing-tokens
Richer path templating and parsing
2 parents 7faef7f + 92e5e95 commit 52bf674

12 files changed

+342
-197
lines changed

.rubocop_todo.yml

+28-25
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1+
RSpec/PendingWithoutReason:
2+
Enabled: false
3+
14
# Offense count: 6
25
RSpec/BeforeAfterAll:
36
Exclude:
4-
- 'spec/jobs/library_scan_job_spec.rb'
5-
- 'spec/jobs/model_file_scan_job_spec.rb'
6-
- 'spec/jobs/model_scan_job_spec.rb'
7-
- 'spec/requests/creators_request_spec.rb'
8-
- 'spec/requests/libraries_request_spec.rb'
9-
- 'spec/requests/models_request_spec.rb'
7+
- "spec/jobs/library_scan_job_spec.rb"
8+
- "spec/jobs/model_file_scan_job_spec.rb"
9+
- "spec/jobs/model_scan_job_spec.rb"
10+
- "spec/requests/creators_request_spec.rb"
11+
- "spec/requests/libraries_request_spec.rb"
12+
- "spec/requests/models_request_spec.rb"
1013

1114
# Offense count: 4
1215
# Configuration parameters: Prefixes, AllowedPatterns.
1316
# Prefixes: when, with, without
1417
RSpec/ContextWording:
1518
Exclude:
16-
- 'spec/jobs/model_scan_job_spec.rb'
17-
- 'spec/models/model_spec.rb'
19+
- "spec/jobs/model_scan_job_spec.rb"
20+
- "spec/models/model_spec.rb"
1821

1922
# Offense count: 6
2023
# Configuration parameters: CountAsOne.
@@ -25,9 +28,9 @@ RSpec/ExampleLength:
2528
# Configuration parameters: AssignmentOnly.
2629
RSpec/InstanceVariable:
2730
Exclude:
28-
- 'spec/models/model_spec.rb'
29-
- 'spec/requests/libraries_request_spec.rb'
30-
- 'spec/requests/models_request_spec.rb'
31+
- "spec/models/model_spec.rb"
32+
- "spec/requests/libraries_request_spec.rb"
33+
- "spec/requests/models_request_spec.rb"
3134

3235
# Offense count: 14
3336
RSpec/MultipleExpectations:
@@ -36,55 +39,55 @@ RSpec/MultipleExpectations:
3639
# Offense count: 4
3740
RSpec/RepeatedExample:
3841
Exclude:
39-
- 'spec/models/model_spec.rb'
42+
- "spec/models/model_spec.rb"
4043

4144
# Offense count: 1
4245
# Configuration parameters: Include.
4346
# Include: db/migrate/*.rb
4447
Rails/CreateTableWithTimestamps:
4548
Exclude:
46-
- 'db/migrate/20220612220116_create_active_storage_variant_records.active_storage.rb'
49+
- "db/migrate/20220612220116_create_active_storage_variant_records.active_storage.rb"
4750

4851
# Offense count: 2
4952
Rails/I18nLocaleTexts:
5053
Exclude:
51-
- 'app/admin/delayed_job.rb'
54+
- "app/admin/delayed_job.rb"
5255

5356
# Offense count: 1
5457
# Configuration parameters: Include.
5558
# Include: db/migrate/*.rb
5659
Rails/NotNullColumn:
5760
Exclude:
58-
- 'db/migrate/20220614211256_add_username_to_users.rb'
61+
- "db/migrate/20220614211256_add_username_to_users.rb"
5962

6063
# Offense count: 4
6164
Rails/OutputSafety:
6265
Exclude:
63-
- 'app/admin/delayed_job.rb'
64-
- 'app/helpers/application_helper.rb'
65-
- 'config/initializers/field_with_errors.rb'
66+
- "app/admin/delayed_job.rb"
67+
- "app/helpers/application_helper.rb"
68+
- "config/initializers/field_with_errors.rb"
6669

6770
# Offense count: 1
6871
# Configuration parameters: Include.
6972
# Include: db/**/*.rb
7073
Rails/ReversibleMigration:
7174
Exclude:
72-
- 'db/migrate/20220106220519_remove_images_table.rb'
75+
- "db/migrate/20220106220519_remove_images_table.rb"
7376

7477
# Offense count: 2
7578
# Configuration parameters: ForbiddenMethods, AllowedMethods.
7679
# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
7780
Rails/SkipsModelValidations:
7881
Exclude:
79-
- 'app/admin/delayed_job.rb'
80-
- 'db/migrate/20220612220115_add_service_name_to_active_storage_blobs.active_storage.rb'
82+
- "app/admin/delayed_job.rb"
83+
- "db/migrate/20220612220115_add_service_name_to_active_storage_blobs.active_storage.rb"
8184

8285
# Offense count: 4
8386
# Configuration parameters: Include.
8487
# Include: app/models/**/*.rb
8588
Rails/UniqueValidationWithoutIndex:
8689
Exclude:
87-
- 'app/models/library.rb'
88-
- 'app/models/model.rb'
89-
- 'app/models/model_file.rb'
90-
- 'app/models/problem.rb'
90+
- "app/models/library.rb"
91+
- "app/models/model.rb"
92+
- "app/models/model_file.rb"
93+
- "app/models/problem.rb"

app/controllers/settings_controller.rb

+50-28
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,62 @@ def show
66
end
77

88
def update
9-
if params[:pagination]
10-
@user.pagination_settings["models"] = params[:pagination][:models] == "1"
11-
@user.pagination_settings["creators"] = params[:pagination][:creators] == "1"
12-
@user.pagination_settings["per_page"] = params[:pagination][:per_page].to_i
13-
end
14-
if current_user.admin? && params[:model_tags]
15-
SiteSettings.model_tags_cloud_threshhold = params[:model_tags][:cloud_threshhold]
16-
SiteSettings.model_tags_cloud_heatmap = params[:model_tags][:cloud_heatmap] == "1"
17-
SiteSettings.model_tags_cloud_keypair = params[:model_tags][:cloud_keypair] == "1"
18-
SiteSettings.model_tags_cloud_sorting = params[:model_tags][:cloud_sorting]
19-
SiteSettings.model_tags_filter_stop_words = params[:model_tags][:filter_stop_words] == "1"
20-
SiteSettings.model_tags_tag_model_directory_name = params[:model_tags][:tag_model_directory_name] == "1"
21-
SiteSettings.model_tags_stop_words_locale = params[:model_tags][:stop_words_locale]
22-
SiteSettings.model_tags_custom_stop_words = params[:model_tags][:custom_stop_words].split
23-
SiteSettings.model_tags_auto_tag_new = params[:model_tags][:auto_tag_new]
24-
SiteSettings.model_path_prefix_template = params[:model_tags][:model_path_prefix_template]
25-
SiteSettings.model_tags_tag_model_path_prefix = params[:model_tags][:model_tags_tag_model_path_prefix]
26-
SiteSettings.model_path_suffix_model_id = params[:model_tags][:model_path_suffix_model_id]
27-
end
28-
if params[:renderer]
29-
@user.renderer_settings["grid_width"] = params[:renderer][:grid_width].to_i
30-
@user.renderer_settings["grid_depth"] = params[:renderer][:grid_width].to_i # Store width in both for now. See #834
31-
@user.renderer_settings["show_grid"] = params[:renderer][:show_grid] == "1"
32-
@user.renderer_settings["enable_pan_zoom"] = params[:renderer][:enable_pan_zoom] == "1"
33-
@user.renderer_settings["background_colour"] = params[:renderer][:background_colour]
34-
@user.renderer_settings["object_colour"] = params[:renderer][:object_colour]
35-
@user.renderer_settings["render_style"] = params[:renderer][:render_style]
36-
end
9+
# Save personal settings
10+
update_pagination_settings(params[:pagination])
11+
update_renderer_settings(params[:renderer])
3712
@user.save!
13+
# Save site-wide settings if user is an admin
14+
if current_user.admin?
15+
update_folder_settings(params[:folders])
16+
update_tagging_settings(params[:model_tags])
17+
end
3818
redirect_to user_settings_path(@user)
3919
end
4020

4121
private
4222

23+
def update_pagination_settings(settings)
24+
return unless settings
25+
@user.pagination_settings = {
26+
"models" => settings[:models] == "1",
27+
"creators" => settings[:creators] == "1",
28+
"per_page" => settings[:per_page].to_i
29+
}
30+
end
31+
32+
def update_renderer_settings(settings)
33+
return unless settings
34+
@user.renderer_settings = {
35+
"grid_width" => settings[:grid_width].to_i,
36+
"grid_depth" => settings[:grid_width].to_i, # Store width in both for now. See #834
37+
"show_grid" => settings[:show_grid] == "1",
38+
"enable_pan_zoom" => settings[:enable_pan_zoom] == "1",
39+
"background_colour" => settings[:background_colour],
40+
"object_colour" => settings[:object_colour],
41+
"render_style" => settings[:render_style]
42+
}
43+
end
44+
45+
def update_folder_settings(settings)
46+
return unless settings
47+
SiteSettings.model_path_template = settings[:model_path_template].gsub(/^\//, "") # Remove leading slashes
48+
SiteSettings.parse_metadata_from_path = settings[:parse_metadata_from_path]
49+
SiteSettings.safe_folder_names = settings[:safe_folder_names]
50+
end
51+
52+
def update_tagging_settings(settings)
53+
return unless settings
54+
SiteSettings.model_tags_cloud_threshhold = settings[:cloud_threshhold]
55+
SiteSettings.model_tags_cloud_heatmap = settings[:cloud_heatmap] == "1"
56+
SiteSettings.model_tags_cloud_keypair = settings[:cloud_keypair] == "1"
57+
SiteSettings.model_tags_cloud_sorting = settings[:cloud_sorting]
58+
SiteSettings.model_tags_filter_stop_words = settings[:filter_stop_words] == "1"
59+
SiteSettings.model_tags_tag_model_directory_name = settings[:tag_model_directory_name] == "1"
60+
SiteSettings.model_tags_stop_words_locale = settings[:stop_words_locale]
61+
SiteSettings.model_tags_custom_stop_words = settings[:custom_stop_words].split
62+
SiteSettings.model_tags_auto_tag_new = settings[:auto_tag_new]
63+
end
64+
4365
def get_user
4466
@user = User.find_by(username: params[:user_id])
4567
end

app/jobs/model_scan_job.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ def perform(model)
4141
model.model_files.reload
4242
model.preview_file = model.model_files.min_by { |x| x.is_image? ? 0 : 1 } unless model.preview_file
4343
if model.tags.empty?
44-
model.autogenerate_tags_from_path!
44+
model.generate_tags_from_directory_name! if SiteSettings.model_tags_tag_model_directory_name
4545
if SiteSettings.model_tags_auto_tag_new.present?
4646
model.tag_list << SiteSettings.model_tags_auto_tag_new
4747
model.save!
4848
end
4949
end
50-
if !model.creator_id
51-
model.autogenerate_creator_from_prefix_template!
50+
if !model.creator_id && SiteSettings.parse_metadata_from_path
51+
model.parse_metadata_from_path!
5252
end
5353
# If this model has no files, flag a problem
5454
if model.model_files.reload.count == 0

app/models/concerns/path_builder.rb

+21-9
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,31 @@ module PathBuilder
22
extend ActiveSupport::Concern
33

44
def formatted_path
5-
formatted_path_out = []
6-
SiteSettings.model_path_prefix_template.split("/").each { |p|
7-
case p
5+
SiteSettings.model_path_template.gsub(/{.+?}/) do |token|
6+
case token
87
when "{tags}"
9-
formatted_path_out.push(tags.order(taggings_count: :desc).map(&:to_s).map(&:parameterize))
8+
(tags.count > 0) ?
9+
File.join(tags.order(taggings_count: :desc).map(&:to_s).map(&:parameterize)) :
10+
"@untagged"
1011
when "{creator}"
11-
formatted_path_out.push(creator ? creator.name : "unset-creator")
12+
safe(creator&.name) || "@unattributed"
1213
when "{collection}"
13-
formatted_path_out.push((collections.count > 0) ? collections.map { |c| c.name } : "unset-collection")
14+
(collections.count > 0) ?
15+
collections.map(&:name).map { |s| safe(s) }.join(",") :
16+
"@uncollected"
17+
when "{modelName}"
18+
safe(name)
19+
when "{modelId}"
20+
"##{id}"
1421
else
15-
formatted_path_out.push("bad-formatted-path-element")
22+
token
1623
end
17-
}
18-
File.join("", formatted_path_out, name.parameterize) + (SiteSettings.model_path_suffix_model_id ? "##{id}" : "")
24+
end
25+
end
26+
27+
private
28+
29+
def safe(str)
30+
SiteSettings.safe_folder_names ? str&.parameterize : str
1931
end
2032
end

app/models/concerns/path_parser.rb

+39-52
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,50 @@
11
module PathParser
22
extend ActiveSupport::Concern
33

4-
def autogenerate_tags_from_path!
5-
tags = []
6-
7-
# Auto-tag based on model directory name:
8-
if SiteSettings.model_tags_tag_model_directory_name
9-
tags = File.split(path).last.split(/[\W_+-]/).filter { |x| x.length > 1 }
10-
end
4+
def generate_tags_from_directory_name!
5+
tags = File.split(path).last.split(/[\W_+-]/).filter { |x| x.length > 1 }
6+
tag_list.add(remove_stop_words(tags))
7+
end
118

12-
unless tags.empty?
13-
tag_list.add(remove_stop_words(tags))
14-
save!
9+
def parse_metadata_from_path!
10+
return unless SiteSettings.model_path_template
11+
components = extract_path_components
12+
if components[:tags].present?
13+
tag_list.add(remove_stop_words(components[:tags]))
1514
end
15+
self.creator = Creator.find_or_create_by(name: components[:creator]) if components[:creator]
16+
collection_list.add(components[:collection]) if components[:collection]
17+
save!
1618
end
19+
end
1720

18-
def autogenerate_creator_from_prefix_template!
19-
if SiteSettings.model_tags_tag_model_path_prefix
20-
filepaths = File.dirname(path).split("/")
21-
filepaths.shift
22-
templatechunks = SiteSettings.model_path_prefix_template.split("/")
23-
creatornew = ""
24-
collectionnew = ""
25-
tags = []
26-
while !templatechunks.empty? && !filepaths.empty? && !(templatechunks.length == 1 && templatechunks[0] == "{tags}")
27-
if templatechunks[0] == "{creator}"
28-
creatornew = filepaths.shift
29-
templatechunks.shift
30-
elsif templatechunks[0] == "{collection}"
31-
collectionnew = filepaths.shift
32-
templatechunks.shift
33-
elsif templatechunks[-1] == "{creator}"
34-
creatornew = filepaths.pop
35-
templatechunks.pop
36-
elsif templatechunks[-1] == "{collection}"
37-
collectionnew = filepaths.pop
38-
templatechunks.pop
39-
else
40-
logger.error "Invalid model_path_prefix_template: #{SiteSettings.model_path_prefix_template} / #{templatechunks[0]}"
41-
templatechunks.shift
42-
end
43-
end
44-
if templatechunks.length == 1 && templatechunks[0] == "{tags}"
45-
tags = filepaths
46-
end
47-
unless tags.empty?
48-
tag_list.add(remove_stop_words(tags))
21+
def path_parse_pattern
22+
Regexp.new("^/?.*?" +
23+
SiteSettings.model_path_template.gsub(/{.+?}/) { |token|
24+
case token
25+
when "{tags}"
26+
"(?<tags>[[:print:]]*)"
27+
when "{creator}"
28+
"(?<creator>[[:print:]&&[^/]]*?)"
29+
when "{collection}"
30+
"(?<collection>[[:print:]&&[^/]]*?)"
31+
when "{modelName}"
32+
"(?<model_name>[[:print:]&&[^/]]*?)"
33+
when "{modelId}"
34+
"(?<model_id>#[[:digit:]]+)?"
35+
else
36+
"[[:print:]&&[^/]]*"
4937
end
50-
unless creatornew.empty?
51-
creator = Creator.find_by(name: creatornew)
52-
creator ||= Creator.create(name: creatornew)
53-
self.creator_id = creator.id
54-
end
55-
unless collectionnew.empty? && !collection_list
56-
collection_list.add(collectionnew)
57-
end
58-
save!
59-
end
60-
end
38+
} + "$")
39+
end
40+
41+
def extract_path_components
42+
components = path.match(path_parse_pattern)&.named_captures&.symbolize_keys
43+
return {} if components.nil?
44+
components.merge({
45+
tags: components[:tags]&.split("/")&.compact_blank,
46+
model_id: nil # discard ID, never gonna use it in parsing
47+
}).compact
6148
end
6249

6350
def remove_stop_words(words)

app/models/site_settings.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ class SiteSettings < RailsSettings::Base
77
field :model_tags_cloud_keypair, type: :boolean, default: true
88
field :model_tags_cloud_sorting, type: :string, default: "frequency"
99
field :model_tags_filter_stop_words, type: :boolean, default: true
10-
field :model_tags_tag_model_directory_name, type: :boolean, default: true
10+
field :model_tags_tag_model_directory_name, type: :boolean, default: false
1111
field :model_tags_stop_words_locale, type: :string, default: "en"
1212
field :model_tags_custom_stop_words, type: :array, default: (SupportedMimeTypes.image_extensions + SupportedMimeTypes.model_extensions)
1313
field :model_tags_auto_tag_new, type: :string, default: "!new"
14-
field :model_path_prefix_template, type: :string, default: "tags"
15-
field :model_tags_tag_model_path_prefix, type: :boolean, default: true
16-
field :model_path_suffix_model_id, type: :boolean, default: true
14+
field :model_path_template, type: :string, default: "{tags}/{modelName}{modelId}"
15+
field :parse_metadata_from_path, type: :boolean, default: true
16+
field :safe_folder_names, type: :boolean, default: true
1717
end

0 commit comments

Comments
 (0)