feat: add preset rule import functionality and update related messages

This commit is contained in:
krau
2026-06-26 15:39:14 +08:00
parent f02860ff3f
commit 2bc460c609
7 changed files with 167 additions and 2 deletions

55
pkg/rule/preset.go Normal file
View File

@@ -0,0 +1,55 @@
package rule
import "path"
// PresetCategory describes a built-in filetype classification: files whose name
// matches Regex are routed into the Dir subdirectory (joined with a user base path).
type PresetCategory struct {
// Name is a stable identifier for the category (used in logs/messages).
Name string
// Regex is a FILENAME-REGEX rule data string matching this category's extensions.
Regex string
// Dir is the default subdirectory name for this category.
Dir string
}
// presetCategories holds the default filetype classification rules.
// Regexes are case-insensitive and match common file extensions.
var presetCategories = []PresetCategory{
{
Name: "video",
Regex: `(?i)\.(mp4|mkv|ts|avi|flv|mov|webm|wmv|rmvb|m2ts)$`,
Dir: "视频",
},
{
Name: "image",
Regex: `(?i)\.(jpg|jpeg|png|gif|webp|bmp)$`,
Dir: "图片",
},
{
Name: "audio",
Regex: `(?i)\.(mp3|flac|wav|aac|m4a|ogg)$`,
Dir: "音频",
},
{
Name: "document",
Regex: `(?i)\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|md|csv|epub|mobi|azw3|chm)$`,
Dir: "文档",
},
{
Name: "archive",
Regex: `(?i)\.(zip|rar|7z|tar|gz|bz2|xz|r\d{1,3}|z\d{1,3}|\d{3}|part\d+\.rar|7z\.\d{3})$`,
Dir: "压缩包",
},
}
// PresetCategories returns the built-in filetype classification rules with each
// category's directory joined under basePath. basePath may be empty.
func PresetCategories(basePath string) []PresetCategory {
out := make([]PresetCategory, len(presetCategories))
for i, c := range presetCategories {
c.Dir = path.Join(basePath, c.Dir)
out[i] = c
}
return out
}

55
pkg/rule/preset_test.go Normal file
View File

@@ -0,0 +1,55 @@
package rule
import (
"regexp"
"testing"
)
func TestPresetCategoriesCompile(t *testing.T) {
for _, c := range PresetCategories("") {
if _, err := regexp.Compile(c.Regex); err != nil {
t.Errorf("preset %q has invalid regex %q: %v", c.Name, c.Regex, err)
}
}
}
func TestPresetCategoriesMatch(t *testing.T) {
cases := map[string]string{
"video": "movie.MP4",
"image": "photo.jpg",
"audio": "song.flac",
"document": "report.pdf",
"archive": "backup.zip",
}
byName := make(map[string]*regexp.Regexp)
for _, c := range PresetCategories("") {
byName[c.Name] = regexp.MustCompile(c.Regex)
}
for name, filename := range cases {
re, ok := byName[name]
if !ok {
t.Errorf("missing preset category %q", name)
continue
}
if !re.MatchString(filename) {
t.Errorf("preset %q did not match %q", name, filename)
}
}
}
func TestPresetCategoriesBasePath(t *testing.T) {
presets := PresetCategories("/media")
for _, c := range presets {
if c.Dir == "" || c.Dir[0] != '/' {
t.Errorf("preset %q dir %q not joined under base path", c.Name, c.Dir)
}
}
// Empty base path must not prefix a separator.
for _, c := range PresetCategories("") {
if c.Dir == "" || c.Dir[0] == '/' {
t.Errorf("preset %q dir %q should be relative when base path empty", c.Name, c.Dir)
}
}
}