Skip to content

Commit cc43305

Browse files
authored
feat(create): support include patterns (#51)
* feat(create): support include patterns * fix readme
1 parent cc4f0ee commit cc43305

File tree

11 files changed

+114
-19
lines changed

11 files changed

+114
-19
lines changed

README.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,18 @@ mkbrr create path/to/file -t https://example-tracker.com/announce -e
168168

169169
# Create a torrent excluding specific file patterns (comma-separated)
170170
mkbrr create path/to/file -t https://example-tracker.com/announce --exclude "*.nfo,*.jpg"
171+
172+
# Create a torrent including only specific file patterns (comma-separated)
173+
mkbrr create path/to/video-folder -t https://example-tracker.com/announce --include "*.mkv,*.mp4"
171174
```
172175

173176
> [!NOTE]
174-
> The exclude patterns feature supports standard glob pattern matching (like `*` for any number of characters, `?` for a single character) and is case-insensitive.
177+
> The exclude and include patterns feature supports standard glob pattern matching (like `*` for any number of characters, `?` for a single character) and is case-insensitive.
178+
> **Precedence:** Inclusion patterns (`--include`) take precedence.
179+
> - If `--include` is used:
180+
> - A file matching an `--include` pattern is **always kept**, even if it also matches an `--exclude` pattern.
181+
> - A file *not* matching any `--include` pattern is **always ignored**.
182+
> - If `--include` is *not* used, then only `--exclude` patterns are considered, and matching files are ignored.
175183
176184
### Inspecting Torrents
177185

@@ -223,7 +231,7 @@ mkbrr create -P ptp --source "MySource" path/to/file
223231
```
224232

225233
> [!TIP]
226-
> The preset file can be placed in the current directory, `~/.config/mkbrr/`, or `~/.mkbrr/`. You can also specify a custom location with `--preset-file`. Presets support the `exclude_patterns` field, allowing you to define default or preset-specific file exclusions.
234+
> The preset file can be placed in the current directory, `~/.config/mkbrr/`, or `~/.mkbrr/`. You can also specify a custom location with `--preset-file`. Presets support both `exclude_patterns` and `include_patterns` fields, allowing you to define default or preset-specific file filtering.
227235
228236
### Batch Mode
229237

@@ -236,7 +244,7 @@ mkbrr create -b batch.yaml
236244
See [batch example](examples/batch.yaml) here.
237245

238246
> [!TIP]
239-
> Batch mode processes jobs in parallel (up to 4 at once) and shows a summary when complete. Batch mode also support the `exclude_patterns` field.
247+
> Batch mode processes jobs in parallel (up to 4 at once) and shows a summary when complete. Batch mode also supports both `exclude_patterns` and `include_patterns` fields.
240248
241249
## Tracker-Specific Features
242250

cmd/create.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ var (
3232
quiet bool
3333
skipPrefix bool
3434
excludePatterns []string
35+
includePatterns []string
3536
)
3637

3738
var createCmd = &cobra.Command{
@@ -104,7 +105,8 @@ func init() {
104105
createCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "be verbose")
105106
createCmd.Flags().BoolVar(&quiet, "quiet", false, "reduced output mode (prints only final torrent path)")
106107
createCmd.Flags().BoolVarP(&skipPrefix, "skip-prefix", "", false, "don't add tracker domain prefix to output filename")
107-
createCmd.Flags().StringArrayVarP(&excludePatterns, "exclude", "", nil, "exclude files matching these patterns (e.g., \"*.nfo,*.jpg\" or -e \"*.nfo\" -e \"*.jpg\")")
108+
createCmd.Flags().StringArrayVarP(&excludePatterns, "exclude", "", nil, "exclude files matching these patterns (e.g., \"*.nfo,*.jpg\" or --exclude \"*.nfo\" --exclude \"*.jpg\")")
109+
createCmd.Flags().StringArrayVarP(&includePatterns, "include", "", nil, "include only files matching these patterns (e.g., \"*.mkv,*.mp4\" or --include \"*.mkv\" --include \"*.mp4\")")
108110

109111
createCmd.Flags().String("cpuprofile", "", "write cpu profile to file (development flag)")
110112

@@ -226,11 +228,15 @@ func runCreate(cmd *cobra.Command, args []string) error {
226228
Entropy: entropy,
227229
Quiet: quiet,
228230
ExcludePatterns: []string{},
231+
IncludePatterns: []string{},
229232
}
230233

231234
if len(presetOpts.ExcludePatterns) > 0 {
232235
opts.ExcludePatterns = slices.Clone(presetOpts.ExcludePatterns)
233236
}
237+
if len(presetOpts.IncludePatterns) > 0 {
238+
opts.IncludePatterns = slices.Clone(presetOpts.IncludePatterns)
239+
}
234240

235241
if presetOpts.PieceLength != 0 {
236242
pieceLen := presetOpts.PieceLength
@@ -276,6 +282,9 @@ func runCreate(cmd *cobra.Command, args []string) error {
276282
if cmd.Flags().Changed("exclude") {
277283
opts.ExcludePatterns = append(opts.ExcludePatterns, excludePatterns...)
278284
}
285+
if cmd.Flags().Changed("include") {
286+
opts.IncludePatterns = append(opts.IncludePatterns, includePatterns...)
287+
}
279288
} else {
280289
// use command line options
281290
opts = torrent.CreateTorrentOptions{
@@ -295,6 +304,7 @@ func runCreate(cmd *cobra.Command, args []string) error {
295304
Quiet: quiet,
296305
SkipPrefix: skipPrefix,
297306
ExcludePatterns: excludePatterns,
307+
IncludePatterns: includePatterns,
298308
}
299309
}
300310

examples/batch.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ jobs:
1818
exclude_patterns: # Example: exclude NFO files and samples
1919
- "*.nfo"
2020
- "*sample*"
21+
include_patterns: # Example: include only video files
22+
- "*.mkv"
23+
- "*.mp4"
24+
- "*.avi"

examples/presets.yaml

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ default:
1212
# exclude_patterns: # Default list of glob patterns to exclude files
1313
# - "*.bak"
1414
# - "temp.*"
15+
# include_patterns: # Default list of glob patterns to include files (only these will be included)
16+
# - "*.mkv"
17+
# - "*.mp4"
1518

1619
presets:
1720
ptp:
@@ -21,6 +24,9 @@ presets:
2124
exclude_patterns: # Example: exclude NFO files and samples
2225
- "*.nfo"
2326
- "*sample*"
27+
include_patterns: # Example: include only video files
28+
- "*.mkv"
29+
- "*.mp4"
2430

2531
# Public tracker preset with all options shown
2632
public:
@@ -29,5 +35,4 @@ presets:
2935
no_creator: true
3036
trackers:
3137
- "udp://tracker.opentrackr.org:1337/announce"
32-
- "udp://open.tracker.cl:1337/announce"
33-
- "udp://9.rarbg.com:2810/announce"
38+

internal/preset/preset.go

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Options struct {
3232
NoCreator *bool `yaml:"no_creator"`
3333
SkipPrefix *bool `yaml:"skip_prefix"`
3434
ExcludePatterns []string `yaml:"exclude_patterns"`
35+
IncludePatterns []string `yaml:"include_patterns"`
3536
Version string // used for creator string
3637
}
3738

@@ -127,6 +128,9 @@ func (c *Config) GetPreset(name string) (*Options, error) {
127128
if len(c.Default.ExcludePatterns) > 0 {
128129
merged.ExcludePatterns = c.Default.ExcludePatterns
129130
}
131+
if len(c.Default.IncludePatterns) > 0 {
132+
merged.IncludePatterns = c.Default.IncludePatterns
133+
}
130134
}
131135

132136
// override with preset values if they are set
@@ -163,6 +167,9 @@ func (c *Config) GetPreset(name string) (*Options, error) {
163167
if len(preset.ExcludePatterns) > 0 {
164168
merged.ExcludePatterns = preset.ExcludePatterns
165169
}
170+
if len(preset.IncludePatterns) > 0 {
171+
merged.IncludePatterns = preset.IncludePatterns
172+
}
166173

167174
return &merged, nil
168175
}

internal/torrent/batch.go

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type BatchJob struct {
3030
NoDate bool `yaml:"no_date"`
3131
SkipPrefix bool `yaml:"skip_prefix"`
3232
ExcludePatterns []string `yaml:"exclude_patterns"`
33+
IncludePatterns []string `yaml:"include_patterns"`
3334
}
3435

3536
// ToCreateOptions converts a BatchJob to CreateTorrentOptions
@@ -53,6 +54,7 @@ func (j *BatchJob) ToCreateOptions(verbose bool, quiet bool, version string) Cre
5354
Version: version,
5455
SkipPrefix: j.SkipPrefix,
5556
ExcludePatterns: j.ExcludePatterns,
57+
IncludePatterns: j.IncludePatterns,
5658
}
5759

5860
if j.PieceLength != 0 {

internal/torrent/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func CreateTorrent(opts CreateTorrentOptions) (*Torrent, error) {
170170
}
171171
return nil
172172
}
173-
if shouldIgnoreFile(filePath, opts.ExcludePatterns) {
173+
if shouldIgnoreFile(filePath, opts.ExcludePatterns, opts.IncludePatterns) {
174174
return nil
175175
}
176176
files = append(files, fileEntry{

internal/torrent/ignore.go

+48-11
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,82 @@ import (
55
"strings"
66
)
77

8-
// file patterns to ignore in source directory (case insensitive)
8+
// file patterns to ignore in source directory (case insensitive) - These are always ignored.
99
var ignoredPatterns = []string{
1010
".torrent",
1111
".ds_store",
1212
"thumbs.db",
1313
"desktop.ini",
1414
}
1515

16-
// shouldIgnoreFile checks if a file should be ignored based on predefined patterns
17-
// and user-defined exclude patterns (glob matching).
18-
func shouldIgnoreFile(path string, excludePatterns []string) bool {
19-
// first check built-in patterns (exact suffix match, case insensitive)
16+
// shouldIgnoreFile checks if a file should be ignored based on predefined patterns,
17+
// user-defined include patterns, and user-defined exclude patterns (glob matching).
18+
// Logic:
19+
// 1. Check built-in ignored patterns (always ignored).
20+
// 2. If include patterns are provided:
21+
// - Check if the file matches any include pattern. If yes, KEEP the file (return false).
22+
// - If it does not match any include pattern, IGNORE the file (return true).
23+
//
24+
// 3. If NO include patterns are provided:
25+
// - Check if the file matches any exclude pattern. If yes, IGNORE the file (return true).
26+
//
27+
// 4. If none of the above conditions cause the file to be ignored, KEEP the file (return false).
28+
func shouldIgnoreFile(path string, excludePatterns []string, includePatterns []string) bool {
29+
// 1. Check built-in patterns (always ignored)
2030
lowerPath := strings.ToLower(path)
2131
for _, pattern := range ignoredPatterns {
2232
if strings.HasSuffix(lowerPath, pattern) {
2333
return true
2434
}
2535
}
2636

27-
// then check user-defined exclude patterns (glob matching on filename, case insensitive)
28-
if len(excludePatterns) > 0 {
29-
filename := filepath.Base(path)
30-
lowerFilename := strings.ToLower(filename)
37+
filename := filepath.Base(path)
38+
lowerFilename := strings.ToLower(filename)
3139

32-
for _, patternGroup := range excludePatterns {
40+
// 2. Check include patterns if provided
41+
if len(includePatterns) > 0 {
42+
matchesInclude := false
43+
for _, patternGroup := range includePatterns {
3344
for _, pattern := range strings.Split(patternGroup, ",") {
3445
pattern = strings.TrimSpace(pattern)
3546
if pattern == "" {
3647
continue
3748
}
49+
match, err := filepath.Match(strings.ToLower(pattern), lowerFilename)
50+
if err == nil && match {
51+
matchesInclude = true
52+
break
53+
}
54+
}
55+
if matchesInclude {
56+
break
57+
}
58+
}
59+
60+
if matchesInclude {
61+
return false // Keep the file because it matches an include pattern
62+
} else {
63+
return true // Ignore the file because include patterns were given, but none matched
64+
}
65+
}
3866

67+
// 3. If NO include patterns were provided, check exclude patterns
68+
if len(excludePatterns) > 0 {
69+
for _, patternGroup := range excludePatterns {
70+
for _, pattern := range strings.Split(patternGroup, ",") {
71+
pattern = strings.TrimSpace(pattern)
72+
if pattern == "" {
73+
continue
74+
}
3975
match, err := filepath.Match(strings.ToLower(pattern), lowerFilename)
4076
// we ignore the error from filepath.Match as malformed patterns simply won't match
4177
if err == nil && match {
42-
return true
78+
return true // Ignore if it matches an exclude pattern (and no include patterns were specified)
4379
}
4480
}
4581
}
4682
}
4783

84+
// 4. Keep the file if no ignore conditions were met
4885
return false
4986
}

internal/torrent/types.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type CreateTorrentOptions struct {
2626
Quiet bool
2727
SkipPrefix bool
2828
ExcludePatterns []string
29+
IncludePatterns []string
2930
}
3031

3132
// Torrent represents a torrent file with additional functionality

schema/batch.json

+7
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171
"items": {
7272
"type": "string"
7373
}
74+
},
75+
"include_patterns": {
76+
"type": "array",
77+
"description": "List of glob patterns to include files (e.g., \"*.mkv\", \"*video*\")",
78+
"items": {
79+
"type": "string"
80+
}
7481
}
7582
}
7683
}

schema/presets.json

+15-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@
5858
"items": {
5959
"type": "string"
6060
}
61+
},
62+
"include_patterns": {
63+
"type": "array",
64+
"description": "List of glob patterns to include files (e.g., \"*.mkv\", \"*video*\")",
65+
"items": {
66+
"type": "string"
67+
}
6168
}
6269
}
6370
},
@@ -116,7 +123,14 @@
116123
"type": "array",
117124
"description": "List of glob patterns to exclude files (e.g., \"*.nfo\", \"*sample*\")",
118125
"items": {
119-
"type": "string"
126+
"type": "string"
127+
}
128+
},
129+
"include_patterns": {
130+
"type": "array",
131+
"description": "List of glob patterns to include files (e.g., \"*.mkv\", \"*video*\")",
132+
"items": {
133+
"type": "string"
120134
}
121135
}
122136
}

0 commit comments

Comments
 (0)