Merge pull request #52 from SebastianMC/50-regexp-and-by-name-support-for-target-folder
#50 regexp and by name support for target folder
This commit is contained in:
commit
7a157464be
187
docs/manual.md
187
docs/manual.md
|
@ -1,5 +1,5 @@
|
||||||
> Document is partial, creation in progress
|
> Document is partial, creation in progress
|
||||||
> Please refer to [README.md](../README.md) for more usage examples
|
> Please refer to [README.md](../README.md) and [advanced-README.md](../advanced-README.md) for more usage examples
|
||||||
> Check also [syntax-reference.md](./syntax-reference.md)
|
> Check also [syntax-reference.md](./syntax-reference.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -70,7 +70,7 @@ For clarity: the three available prefixes `/!` and `/!!` and `/!!!` allow for fu
|
||||||
|
|
||||||
## Simple wildcards
|
## Simple wildcards
|
||||||
|
|
||||||
Currently, the below simple wildcard syntax is supported:
|
Currently, the below simple wildcard syntax is supported for sorting group:
|
||||||
|
|
||||||
### A single digit (exactly one)
|
### A single digit (exactly one)
|
||||||
|
|
||||||
|
@ -248,3 +248,186 @@ sorting-spec: |
|
||||||
/! starred:
|
/! starred:
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Options for target-folder: matching
|
||||||
|
|
||||||
|
The `target-folder:` has the following variants, listed in the order of precedence:
|
||||||
|
|
||||||
|
1. match by the **exact folder path** (the default)
|
||||||
|
2. match by the **exact folder name**
|
||||||
|
3. match by **regexp** (for experts, be careful!)
|
||||||
|
4. match by **wildcard suffix** (aka match folders subtree)
|
||||||
|
|
||||||
|
If a folder in the vault matches more than one `target-folder:` definitions,
|
||||||
|
the above list shows the precedence, e.g. 1. has precedence over 2., 3. and 4. for example.
|
||||||
|
In other words, match by exact folder path always wins, then goes the match by folder exact name,
|
||||||
|
and so on.
|
||||||
|
|
||||||
|
If a folder in the vault matches more than one `target-folder:` definitions of the same type,
|
||||||
|
see the detailed description below for the behavior
|
||||||
|
|
||||||
|
### By folder path (the default)
|
||||||
|
|
||||||
|
If no additional modifiers follow the `target-folder:`, the remaining part of the line
|
||||||
|
is treated as an exact folder path (leading and trailing spaces are ignored,
|
||||||
|
infix spaces are treated literally as part of the folder path)
|
||||||
|
|
||||||
|
Within the same vault duplicate definitions of same path in `target-folder:` are detected
|
||||||
|
and error is raised in that case, indicating the duplicated path
|
||||||
|
|
||||||
|
Examples of `target-folder:` with match by the exact folder path:
|
||||||
|
|
||||||
|
- `target-folder: My Folder`
|
||||||
|
- this refers to the folder in the root of the vault and only to it
|
||||||
|
- `target-folder: Archive/My Folder`
|
||||||
|
- matches the `My Folder` sub-folder in the `Archive` folder (a sub-folder of the root)
|
||||||
|
- `target-folder: .`
|
||||||
|
- this refers to path of the folder where the sorting specification resides (the specification containing the line,
|
||||||
|
keep in mind that the sorting specification can reside in multiple locations in multiple notes)
|
||||||
|
- `target-folder: ./Some Subfolder`
|
||||||
|
- this refers to path of a sub-folder of the folder where the sorting specification resides (the specification containing the line,
|
||||||
|
keep in mind that the sorting specification can reside in multiple locations in multiple notes)
|
||||||
|
|
||||||
|
### By folder name
|
||||||
|
|
||||||
|
The modifier `name:` tells the `target-folder:` to match the folder name and not the full folder path
|
||||||
|
|
||||||
|
This is an exact match of the full folder name, no partial matching
|
||||||
|
|
||||||
|
Within the same vault duplicate definitions of same name in `target-folder: name:` are detected
|
||||||
|
and error is raised in that case, indicating the duplicated folder name in sorting specification
|
||||||
|
|
||||||
|
Examples of `target-folder:` with match by the exact folder name:
|
||||||
|
|
||||||
|
- `target-folder: name: My Folder`
|
||||||
|
- matches all the folders with the name `My Folder` regardless of their location within the vault
|
||||||
|
|
||||||
|
### By regexp (expert feature)
|
||||||
|
|
||||||
|
> WARNING!!! This is an EXPERT FEATURE.
|
||||||
|
>
|
||||||
|
> Involving and constructing the regexp-s requires at least basic knowledge about the potential pitfalls.\
|
||||||
|
> If you introduce a heavy _regexp-backtracking_ it can **kill performance of Obsidian and even make it unresponsive**\
|
||||||
|
> If you don't know what the _regexp-backtracking_ is, be careful when using regexp for `target-folder:`
|
||||||
|
|
||||||
|
The modifier `regexp:` tells the `target-folder:` to involve the specified regular expressions in matching
|
||||||
|
|
||||||
|
Additional dependent modifiers are supported for `regexp:`:
|
||||||
|
- `for-name:`
|
||||||
|
- tells the matching to be done against the folder name, not the full path
|
||||||
|
- `debug:`
|
||||||
|
- tells the regexp to report its match in the developer console, so that you can easily investigate
|
||||||
|
why the regexp matches (or why it doesn't match) as expected
|
||||||
|
- `/!:` `/!!:` `/!!!:`
|
||||||
|
- sets the priority of the regexp
|
||||||
|
|
||||||
|
By default, the regexp is matched against the full path of the folder, unless the `for-name:` modifiers tells otherwise.
|
||||||
|
|
||||||
|
By default, the regexp-es have no priority and are evaluated in the order of their definition.\
|
||||||
|
If you store `sorting-spec:` configurations in notes spread all over the vault,
|
||||||
|
consider the order of `target-folder: regexp:` to be undefined and - if needed - use
|
||||||
|
explicit priority modifiers (`/!:` `/!!:` `/!!!:`) to impose the desired order of matching.
|
||||||
|
- a regexp with modifier `/!!!:` if evaluated before all other regexps, regardless of where they are configured
|
||||||
|
- if two or more regexps are stamped with `/!!!:`, they are matched in the order in which they were defined.\
|
||||||
|
Within a single YAML section of a note the order is obvious.\
|
||||||
|
For sorting specifications spread over many notes in the vault consider the order to be undefined.
|
||||||
|
- a regexp with modifier `/!!:` if evaluated after any `/!!!:` and before all other regexps
|
||||||
|
- the same logic as described above applies when multiple regexps have the `/!!:` stamp
|
||||||
|
- a regexp with modifier `/!:` indicates the lowest of explicitly defined priorities.\
|
||||||
|
Such a regexp is matched after all priority-stamped regexps, before the regexps not having
|
||||||
|
any explicit priority stamp
|
||||||
|
|
||||||
|
The escape character is \ - the standard one in regexp world.
|
||||||
|
|
||||||
|
Examples of `target-folder:` with match by regexp:
|
||||||
|
|
||||||
|
- `target-folder: regexp: reading`
|
||||||
|
- matches any folder which contains the word `reading` in its path or name
|
||||||
|
- `target-folder: regexp: \d?\d-\d?\d-\d\d\d\d$`
|
||||||
|
- matches any folder which ends with date-alike numerical expression, e.g.:
|
||||||
|
- `1-1-2023`
|
||||||
|
- `Archive/Monthly/12/05-12-2022`
|
||||||
|
- `Inbox/Not digested notes from 20-7-2019`
|
||||||
|
- `target-folder: regexp: for-name: I am everywhere`
|
||||||
|
- matches all folders which contain the phrase `I am everywhere` in their name, e.g.:
|
||||||
|
- `Reports/Not processed/What the I am everywhere report from Paul means?`
|
||||||
|
- `Chapters/I am everywhere`
|
||||||
|
- `target-folder: regexp: for-name: ^I am (everyw)?here$`
|
||||||
|
- matches all folders with name exactly `I am everywhere` or `I am here`
|
||||||
|
- `target-folder: regexp: for-name: debug: ^...$`
|
||||||
|
- matches all folders with name comprising exactly 3 character
|
||||||
|
- when a folder is matched, a diagnostic line is written to the console - `debug:` modifiers enables the logging
|
||||||
|
- `target-folder: regexp: debug: ^.{13,15}$`
|
||||||
|
- matches all folders with path length between 13 and 15 characters
|
||||||
|
- diagnostic line is written to the console due to `debug:`
|
||||||
|
- `target-folder: regexp: for-name: /!: ^[aA]`
|
||||||
|
- matches all folders with name starting with `a` or `A`
|
||||||
|
- the priority `/!:` modifier causes the matching to be done before all other regexps
|
||||||
|
which don't have any priority
|
||||||
|
- `target-folder: regexp: /!!!: debug: for-name: abc|def|ghi`
|
||||||
|
- matches all folders with name containing the sequence `abc` or `def` or `ghi`
|
||||||
|
- the modifier `/!!!:` imposes the highest priority of regexp matching
|
||||||
|
- `debug:` tells to report each matching folder in the console
|
||||||
|
- `target-folder: regexp: ^[^/]+/[^/]+$`
|
||||||
|
- matches all folders which are at the 2nd level of vault tree, e.g.:
|
||||||
|
- `Inbox/Priority input`
|
||||||
|
- `Archive/2021`
|
||||||
|
- `target-folder: regexp: ^[^\/]+(\/[^\/]+){2}$`
|
||||||
|
- matches all folders which are at the 3rd level of vault tree, e.g.:
|
||||||
|
- `Archive/2019/05`
|
||||||
|
- `Aaaa/Bbbb/Test test`
|
||||||
|
|
||||||
|
### By wildcard
|
||||||
|
|
||||||
|
In the default usage of `target-folder:` with the exact full folder path, if the path contains
|
||||||
|
the `/...` or `/*` suffix its meaning is extended to:
|
||||||
|
- match the folder and all its immediate (child) subfolders - `/...` suffix
|
||||||
|
- match the folder and all its subfolders at any level (all descendants, the entire subtree) - `/*` suffix
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- `target-folder: /*`
|
||||||
|
- matches all folders in the vault (the root folder and all its descendants)
|
||||||
|
- `target-folder: /...`
|
||||||
|
- matches the root folder and its immediate children (aka immediate subfolders of the root)
|
||||||
|
|
||||||
|
If the sorting specification contains duplicate wildcard-ed path in `target-folder:`
|
||||||
|
an error is raised, indicating the duplicate path
|
||||||
|
|
||||||
|
If a folder is matched by two (or more) wildcarded paths, the one with more path segments
|
||||||
|
(the deeper one) wins. For example:
|
||||||
|
- a folder `Book/Chapters/12/a` is matched by:
|
||||||
|
- (a) `target-folder: Book/*`, and
|
||||||
|
- (b) `target-folder: Book/Chapters/*`
|
||||||
|
- In this case the (b) wins, because it contains a deeper path
|
||||||
|
|
||||||
|
If the depth of matches specification is the same, the `/...` takes precedence over `/*`
|
||||||
|
- a folder `Book/Appendix/III` is matched by:
|
||||||
|
- (a) `target-folder: Book/Appendix/...`, and
|
||||||
|
- (b) `target-folder: Book/Appendix/*`
|
||||||
|
- In this case the (a) wins
|
||||||
|
|
||||||
|
## Excluding folders from custom sorting
|
||||||
|
|
||||||
|
Having the ability to wildard- and regexp-based match of `target-folder:` in some cases
|
||||||
|
you might want to exclude folder(s) from custom sorting.
|
||||||
|
|
||||||
|
This can be done by combination of the `target-folder:` (in any of its variants)
|
||||||
|
and specification of the sort order as `sorting: standard`
|
||||||
|
|
||||||
|
An example piece of YAML frontmatter could look like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
sorting-spec: |
|
||||||
|
|
||||||
|
// ... some sorting specification above
|
||||||
|
|
||||||
|
target-folder: Reviews/Attachments
|
||||||
|
target-folder: TODOs
|
||||||
|
sorting: standard
|
||||||
|
|
||||||
|
// ... some sorting specification below
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
|
@ -64,6 +64,7 @@ export interface CustomSortGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomSortSpec {
|
export interface CustomSortSpec {
|
||||||
|
// plays only informative role about the original parsed 'target-folder:' values
|
||||||
targetFoldersPaths: Array<string> // For root use '/'
|
targetFoldersPaths: Array<string> // For root use '/'
|
||||||
defaultOrder?: CustomSortOrder
|
defaultOrder?: CustomSortOrder
|
||||||
byMetadataField?: string // for 'by-metadata:' if the defaultOrder is by metadata alphabetical or reverse
|
byMetadataField?: string // for 'by-metadata:' if the defaultOrder is by metadata alphabetical or reverse
|
||||||
|
|
|
@ -14,6 +14,10 @@ const createMockMatcherRichVersion = (): FolderWildcardMatching<SortingSpec> =>
|
||||||
return matcher
|
return matcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PRIO1 = 1
|
||||||
|
const PRIO2 = 2
|
||||||
|
const PRIO3 = 3
|
||||||
|
|
||||||
const createMockMatcherSimplestVersion = (): FolderWildcardMatching<SortingSpec> => {
|
const createMockMatcherSimplestVersion = (): FolderWildcardMatching<SortingSpec> => {
|
||||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
matcher.addWildcardDefinition('/Reviews/daily/*', '/Reviews/daily/*')
|
matcher.addWildcardDefinition('/Reviews/daily/*', '/Reviews/daily/*')
|
||||||
|
@ -117,4 +121,137 @@ describe('folderMatch', () => {
|
||||||
|
|
||||||
expect(result).toEqual({errorMsg: "Duplicate wildcard '*' specification for Archive/2019/*"})
|
expect(result).toEqual({errorMsg: "Duplicate wildcard '*' specification for Archive/2019/*"})
|
||||||
})
|
})
|
||||||
|
it('regexp-match by name works (order of regexp doesn\'t matter) case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||||
|
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||||
|
// Path with leading /
|
||||||
|
const match1: SortingSpec | null = matcher.folderMatch('/Reviews/daily', 'daily')
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match2: SortingSpec | null = matcher.folderMatch('Reviews/daily', 'daily')
|
||||||
|
expect(match1).toBe('r2')
|
||||||
|
expect(match2).toBe('r2')
|
||||||
|
})
|
||||||
|
it('regexp-match by name works (order of regexp doesn\'t matter) reversed case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||||
|
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||||
|
// Path with leading /
|
||||||
|
const match1: SortingSpec | null = matcher.folderMatch('/Reviews/daily', 'daily')
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match2: SortingSpec | null = matcher.folderMatch('Reviews/daily', 'daily')
|
||||||
|
expect(match1).toBe('r2')
|
||||||
|
expect(match2).toBe('r2')
|
||||||
|
})
|
||||||
|
it('regexp-match by path works (order of regexp doesn\'t matter) case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`)
|
||||||
|
matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`)
|
||||||
|
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||||
|
// Path with leading /
|
||||||
|
const match1: SortingSpec | null = matcher.folderMatch('/Reviews/daily', 'daily')
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match2: SortingSpec | null = matcher.folderMatch('Reviews/daily', 'daily')
|
||||||
|
expect(match1).toBe('w1') // The path-based regexp doesn't match the leading /
|
||||||
|
expect(match2).toBe('r1')
|
||||||
|
})
|
||||||
|
it('regexp-match by path works (order of regexp doesn\'t matter) reversed case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`)
|
||||||
|
matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`)
|
||||||
|
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||||
|
// Path with leading /
|
||||||
|
const match1: SortingSpec | null = matcher.folderMatch('/Reviews/daily', 'daily')
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match2: SortingSpec | null = matcher.folderMatch('Reviews/daily', 'daily')
|
||||||
|
expect(match1).toBe('w1') // The path-based regexp doesn't match the leading /
|
||||||
|
expect(match2).toBe('r1')
|
||||||
|
})
|
||||||
|
it('regexp-match by path and name for root level - order of regexp decides - case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||||
|
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('daily', 'daily')
|
||||||
|
expect(match).toBe('r2')
|
||||||
|
})
|
||||||
|
it('regexp-match by path and name for root level - order of regexp decides - reversed case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||||
|
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('daily', 'daily')
|
||||||
|
expect(match).toBe('r1')
|
||||||
|
})
|
||||||
|
it('regexp-match priorities - order of definitions irrelevant - unique priorities - case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 3, false, `r1p3`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, undefined, false, `r4pNone`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('freq/daily', 'daily')
|
||||||
|
expect(match).toBe('r1p3')
|
||||||
|
})
|
||||||
|
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, undefined, false, `r4pNone`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 3, false, `r1p3`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('freq/daily', 'daily')
|
||||||
|
expect(match).toBe('r1p3')
|
||||||
|
})
|
||||||
|
it('regexp-match priorities - order of definitions irrelevant - duplicate priorities - case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3a`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3b`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, 2, false, `r2p2a`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, 2, false, `r2p2b`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, 1, false, `r3p1a`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, 1, false, `r3p1b`)
|
||||||
|
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r4pNone`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('daily', 'daily')
|
||||||
|
expect(match).toBe('r1p3b')
|
||||||
|
})
|
||||||
|
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, undefined, false, `r4pNone`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
|
||||||
|
matcher.addRegexpDefinition(/^freq\/daily$/, false, 3, false, `r1p3`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('freq/daily', 'daily')
|
||||||
|
expect(match).toBe('r1p3')
|
||||||
|
})
|
||||||
|
it('regexp-match - edge case of matching the root folder - match by path', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
matcher.addRegexpDefinition(/^\/$/, false, undefined, false, `r1`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('/', '')
|
||||||
|
expect(match).toBe('r1')
|
||||||
|
})
|
||||||
|
it('regexp-match - edge case of matching the root folder - match by name not possible', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
// Tricky regexp which can return zero length matches
|
||||||
|
matcher.addRegexpDefinition(/.*/, true, undefined, false, `r1`)
|
||||||
|
matcher.addWildcardDefinition('/*', `w1`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('/', '')
|
||||||
|
expect(match).toBe('w1')
|
||||||
|
})
|
||||||
|
it('regexp-match - edge case of no match when only regexp rules present', () => {
|
||||||
|
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||||
|
// Tricky regexp which can return zero length matches
|
||||||
|
matcher.addRegexpDefinition(/abc/, true, undefined, false, `r1`)
|
||||||
|
// Path w/o leading / - this is how Obsidian supplies the path
|
||||||
|
const match: SortingSpec | null = matcher.folderMatch('/', '')
|
||||||
|
expect(match).toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
export interface FolderPattern {
|
|
||||||
path: string
|
|
||||||
deep: boolean
|
|
||||||
nestingLevel: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DeterminedSortingSpec<SortingSpec> = {
|
export type DeterminedSortingSpec<SortingSpec> = {
|
||||||
spec?: SortingSpec
|
spec?: SortingSpec
|
||||||
}
|
}
|
||||||
|
@ -16,6 +10,14 @@ export interface FolderMatchingTreeNode<SortingSpec> {
|
||||||
subtree: { [key: string]: FolderMatchingTreeNode<SortingSpec> }
|
subtree: { [key: string]: FolderMatchingTreeNode<SortingSpec> }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FolderMatchingRegexp<SortingSpec> {
|
||||||
|
regexp: RegExp
|
||||||
|
againstName: boolean
|
||||||
|
priority: number
|
||||||
|
logMatches: boolean
|
||||||
|
sortingSpec: SortingSpec
|
||||||
|
}
|
||||||
|
|
||||||
const SLASH: string = '/'
|
const SLASH: string = '/'
|
||||||
export const MATCH_CHILDREN_PATH_TOKEN: string = '...'
|
export const MATCH_CHILDREN_PATH_TOKEN: string = '...'
|
||||||
export const MATCH_ALL_PATH_TOKEN: string = '*'
|
export const MATCH_ALL_PATH_TOKEN: string = '*'
|
||||||
|
@ -23,6 +25,7 @@ export const MATCH_CHILDREN_1_SUFFIX: string = `/${MATCH_CHILDREN_PATH_TOKEN}`
|
||||||
export const MATCH_CHILDREN_2_SUFFIX: string = `/${MATCH_CHILDREN_PATH_TOKEN}/`
|
export const MATCH_CHILDREN_2_SUFFIX: string = `/${MATCH_CHILDREN_PATH_TOKEN}/`
|
||||||
export const MATCH_ALL_SUFFIX: string = `/${MATCH_ALL_PATH_TOKEN}`
|
export const MATCH_ALL_SUFFIX: string = `/${MATCH_ALL_PATH_TOKEN}`
|
||||||
|
|
||||||
|
export const NO_PRIORITY = 0
|
||||||
|
|
||||||
export const splitPath = (path: string): Array<string> => {
|
export const splitPath = (path: string): Array<string> => {
|
||||||
return path.split(SLASH).filter((name) => !!name)
|
return path.split(SLASH).filter((name) => !!name)
|
||||||
|
@ -34,10 +37,13 @@ export interface AddingWildcardFailure {
|
||||||
|
|
||||||
export class FolderWildcardMatching<SortingSpec> {
|
export class FolderWildcardMatching<SortingSpec> {
|
||||||
|
|
||||||
|
// mimics the structure of folders, so for example tree.matchAll contains the matchAll flag for the root '/'
|
||||||
tree: FolderMatchingTreeNode<SortingSpec> = {
|
tree: FolderMatchingTreeNode<SortingSpec> = {
|
||||||
subtree: {}
|
subtree: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regexps: Array<FolderMatchingRegexp<SortingSpec>>
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
determinedWildcardRules: { [key: string]: DeterminedSortingSpec<SortingSpec> } = {}
|
determinedWildcardRules: { [key: string]: DeterminedSortingSpec<SortingSpec> } = {}
|
||||||
|
|
||||||
|
@ -76,33 +82,82 @@ export class FolderWildcardMatching<SortingSpec> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
folderMatch = (folderPath: string): SortingSpec | null => {
|
addRegexpDefinition = (regexp: RegExp,
|
||||||
|
againstName: boolean,
|
||||||
|
priority: number | undefined,
|
||||||
|
log: boolean | undefined,
|
||||||
|
rule: SortingSpec
|
||||||
|
) => {
|
||||||
|
const newItem: FolderMatchingRegexp<SortingSpec> = {
|
||||||
|
regexp: regexp,
|
||||||
|
againstName: againstName,
|
||||||
|
priority: priority || NO_PRIORITY,
|
||||||
|
sortingSpec: rule,
|
||||||
|
logMatches: !!log
|
||||||
|
}
|
||||||
|
if (this.regexps === undefined || this.regexps.length === 0) {
|
||||||
|
this.regexps = [newItem]
|
||||||
|
} else {
|
||||||
|
// priority is present ==> consciously determine where to insert the regexp
|
||||||
|
let idx = 0
|
||||||
|
while (idx < this.regexps.length && this.regexps[idx].priority > newItem.priority) {
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
this.regexps.splice(idx, 0, newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
folderMatch = (folderPath: string, folderName?: string): SortingSpec | null => {
|
||||||
const spec: DeterminedSortingSpec<SortingSpec> = this.determinedWildcardRules[folderPath]
|
const spec: DeterminedSortingSpec<SortingSpec> = this.determinedWildcardRules[folderPath]
|
||||||
|
|
||||||
if (spec) {
|
if (spec) {
|
||||||
return spec.spec ?? null
|
return spec.spec ?? null
|
||||||
} else {
|
} else {
|
||||||
let rule: SortingSpec | null | undefined = this.tree.matchChildren
|
let rule: SortingSpec | null | undefined
|
||||||
let inheritedRule: SortingSpec | undefined = this.tree.matchAll
|
// regexp matching
|
||||||
const pathComponents: Array<string> = splitPath(folderPath)
|
if (this.regexps) {
|
||||||
let parentNode: FolderMatchingTreeNode<SortingSpec> = this.tree
|
for (let r of this.regexps) {
|
||||||
let lastIdx: number = pathComponents.length - 1
|
if (r.againstName && !folderName) {
|
||||||
for(let i=0; i<=lastIdx; i++) {
|
// exclude the edge case:
|
||||||
const name: string = pathComponents[i]
|
// - root folder which has empty name (and path /)
|
||||||
let matchedPath: FolderMatchingTreeNode<SortingSpec> = parentNode.subtree[name]
|
// AND name-matching regexp allows zero-length matches
|
||||||
if (matchedPath) {
|
continue
|
||||||
parentNode = matchedPath
|
}
|
||||||
rule = matchedPath?.matchChildren ?? null
|
if (r.regexp.test(r.againstName ? (folderName || '') : folderPath)) {
|
||||||
inheritedRule = matchedPath.matchAll ?? inheritedRule
|
rule = r.sortingSpec
|
||||||
} else {
|
if (r.logMatches) {
|
||||||
if (i < lastIdx) {
|
const msgDetails: string = (r.againstName) ? `name: ${folderName}` : `path: ${folderPath}`
|
||||||
rule = inheritedRule
|
console.log(`custom-sort plugin - regexp <${r.regexp.source}> matched folder ${msgDetails}`)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rule = rule ?? inheritedRule
|
// simple wildards matching
|
||||||
|
if (!rule) {
|
||||||
|
rule = this.tree.matchChildren
|
||||||
|
let inheritedRule: SortingSpec | undefined = this.tree.matchAll
|
||||||
|
const pathComponents: Array<string> = splitPath(folderPath)
|
||||||
|
let parentNode: FolderMatchingTreeNode<SortingSpec> = this.tree
|
||||||
|
let lastIdx: number = pathComponents.length - 1
|
||||||
|
for (let i = 0; i <= lastIdx; i++) {
|
||||||
|
const name: string = pathComponents[i]
|
||||||
|
let matchedPath: FolderMatchingTreeNode<SortingSpec> = parentNode.subtree[name]
|
||||||
|
if (matchedPath) {
|
||||||
|
parentNode = matchedPath
|
||||||
|
rule = matchedPath?.matchChildren ?? null
|
||||||
|
inheritedRule = matchedPath.matchAll ?? inheritedRule
|
||||||
|
} else {
|
||||||
|
if (i < lastIdx) {
|
||||||
|
rule = inheritedRule
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rule = rule ?? inheritedRule
|
||||||
|
}
|
||||||
|
|
||||||
if (rule) {
|
if (rule) {
|
||||||
this.determinedWildcardRules[folderPath] = {spec: rule}
|
this.determinedWildcardRules[folderPath] = {spec: rule}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
CompoundDashNumberNormalizerFn,
|
CompoundDashNumberNormalizerFn,
|
||||||
CompoundDashRomanNumberNormalizerFn,
|
CompoundDashRomanNumberNormalizerFn,
|
||||||
CompoundDotNumberNormalizerFn,
|
CompoundDotNumberNormalizerFn, ConsumedFolderMatchingRegexp, consumeFolderByRegexpExpression,
|
||||||
convertPlainStringToRegex,
|
convertPlainStringToRegex,
|
||||||
detectNumericSortingSymbols,
|
detectNumericSortingSymbols,
|
||||||
escapeRegexUnsafeCharacters,
|
escapeRegexUnsafeCharacters,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
||||||
SortingSpecProcessor
|
SortingSpecProcessor
|
||||||
} from "./sorting-spec-processor"
|
} from "./sorting-spec-processor"
|
||||||
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec} from "./custom-sort-types";
|
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec} from "./custom-sort-types";
|
||||||
import {FolderMatchingTreeNode} from "./folder-matching-rules";
|
import {FolderMatchingRegexp, FolderMatchingTreeNode} from "./folder-matching-rules";
|
||||||
|
|
||||||
const txtInputExampleA: string = `
|
const txtInputExampleA: string = `
|
||||||
order-asc: a-z
|
order-asc: a-z
|
||||||
|
@ -780,6 +780,228 @@ describe('SortingSpecProcessor edge case', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const txtInputTargetFolderByName: string = `
|
||||||
|
target-folder: name: TheName
|
||||||
|
< a-z
|
||||||
|
`
|
||||||
|
|
||||||
|
const txtInputTargetFolderWithRegex: string = `
|
||||||
|
> advanced modified
|
||||||
|
target-folder: name: TheName
|
||||||
|
< a-z
|
||||||
|
target-folder: regexp: r1
|
||||||
|
target-folder: regexp: /!!: r2*
|
||||||
|
target-folder: regexp: for-name: r3.{2-3}$
|
||||||
|
target-folder: regexp: for-name: /!: r4\\d
|
||||||
|
target-folder: regexp: for-name: /!!: ^r5[^[]+
|
||||||
|
target-folder: regexp: for-name: /!!!: ^r6/+$
|
||||||
|
target-folder: regexp: debug: r7 +
|
||||||
|
target-folder: regexp: for-name: debug: r8 (aa|bb|cc)
|
||||||
|
target-folder: regexp: for-name: /!!!: debug: r9 [abc]+
|
||||||
|
target-folder: regexp: /!: debug: ^r10 /[^/]/.+$
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedSortSpecTargetFolderRegexAndName1 = {
|
||||||
|
defaultOrder: CustomSortOrder.byModifiedTimeReverseAdvanced,
|
||||||
|
groups: [{
|
||||||
|
order: CustomSortOrder.byModifiedTimeReverseAdvanced,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 0,
|
||||||
|
targetFoldersPaths: ['mock-folder']
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedSortSpecTargetFolderByName = {
|
||||||
|
defaultOrder: CustomSortOrder.alphabetical,
|
||||||
|
groups: [{
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 0,
|
||||||
|
targetFoldersPaths: ['name: TheName']
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedSortSpecsTargetFolderByPathInRegexTestCase: { [key: string]: CustomSortSpec } = {
|
||||||
|
'mock-folder': expectedSortSpecTargetFolderRegexAndName1
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedSortSpecsTargetFolderByName: { [key: string]: CustomSortSpec } = {
|
||||||
|
'TheName': expectedSortSpecTargetFolderByName
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedSortSpecForRegexpTextCase = {
|
||||||
|
groups: [{
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 0,
|
||||||
|
targetFoldersPaths: [
|
||||||
|
"regexp: r1",
|
||||||
|
"regexp: /!!: r2*",
|
||||||
|
"regexp: for-name: r3.{2-3}$",
|
||||||
|
"regexp: for-name: /!: r4\\d",
|
||||||
|
"regexp: for-name: /!!: ^r5[^[]+",
|
||||||
|
"regexp: for-name: /!!!: ^r6/+$",
|
||||||
|
"regexp: debug: r7 +",
|
||||||
|
"regexp: for-name: debug: r8 (aa|bb|cc)",
|
||||||
|
"regexp: for-name: /!!!: debug: r9 [abc]+",
|
||||||
|
"regexp: /!: debug: ^r10 /[^/]/.+$"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedTargetFolderRegexpArr: Array<FolderMatchingRegexp<CustomSortSpec>> = [
|
||||||
|
{
|
||||||
|
regexp: /r9 [abc]+/,
|
||||||
|
againstName: true,
|
||||||
|
priority: 3,
|
||||||
|
logMatches: true,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /^r6\/+$/,
|
||||||
|
againstName: true,
|
||||||
|
priority: 3,
|
||||||
|
logMatches: false,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /^r5[^[]+/,
|
||||||
|
againstName: true,
|
||||||
|
priority: 2,
|
||||||
|
logMatches: false,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /r2*/,
|
||||||
|
againstName: false,
|
||||||
|
priority: 2,
|
||||||
|
logMatches: false,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /^r10 \/[^/]\/.+$/,
|
||||||
|
againstName: false,
|
||||||
|
priority: 1,
|
||||||
|
logMatches: true,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /r4\d/,
|
||||||
|
againstName: true,
|
||||||
|
priority: 1,
|
||||||
|
logMatches: false,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /r8 (aa|bb|cc)/,
|
||||||
|
againstName: true,
|
||||||
|
priority: 0,
|
||||||
|
logMatches: true,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /r7 +/,
|
||||||
|
againstName: false,
|
||||||
|
priority: 0,
|
||||||
|
logMatches: true,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /r3.{2-3}$/,
|
||||||
|
againstName: true,
|
||||||
|
priority: 0,
|
||||||
|
logMatches: false,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp: /r1/,
|
||||||
|
againstName: false,
|
||||||
|
priority: 0,
|
||||||
|
logMatches: false,
|
||||||
|
sortingSpec: expectedSortSpecForRegexpTextCase
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('SortingSpecProcessor target-folder by name and regex', () => {
|
||||||
|
let processor: SortingSpecProcessor;
|
||||||
|
beforeEach(() => {
|
||||||
|
processor = new SortingSpecProcessor();
|
||||||
|
});
|
||||||
|
it('should correctly handle the by-name only target-folder', () => {
|
||||||
|
const inputTxtArr: Array<string> = txtInputTargetFolderByName.split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result?.sortSpecByPath).toEqual({})
|
||||||
|
expect(result?.sortSpecByName).toEqual(expectedSortSpecsTargetFolderByName)
|
||||||
|
expect(result?.sortSpecByWildcard).not.toBeNull()
|
||||||
|
})
|
||||||
|
it('should recognize and correctly parse target folder by name with and w/o regexp variants', () => {
|
||||||
|
const inputTxtArr: Array<string> = txtInputTargetFolderWithRegex.split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsTargetFolderByPathInRegexTestCase)
|
||||||
|
expect(result?.sortSpecByName).toEqual(expectedSortSpecsTargetFolderByName)
|
||||||
|
expect(result?.sortSpecByWildcard?.tree).toEqual({subtree: {}})
|
||||||
|
expect(result?.sortSpecByWildcard?.regexps).toEqual(expectedTargetFolderRegexpArr)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const NOPRIO = 0
|
||||||
|
const PRIO1 = 1
|
||||||
|
const PRIO2 = 2
|
||||||
|
const PRIO3 = 3
|
||||||
|
|
||||||
|
const consumedTargetFolderRegexp: Array<ConsumedFolderMatchingRegexp> = [
|
||||||
|
{
|
||||||
|
regexp: /r4\d/,
|
||||||
|
againstName: true,
|
||||||
|
priority: undefined,
|
||||||
|
log: true
|
||||||
|
}, {
|
||||||
|
regexp: /r4\d/,
|
||||||
|
againstName: true,
|
||||||
|
priority: PRIO1,
|
||||||
|
log: true
|
||||||
|
}, {
|
||||||
|
regexp: /r4\d/,
|
||||||
|
againstName: true,
|
||||||
|
priority: PRIO2,
|
||||||
|
log: true
|
||||||
|
}, {
|
||||||
|
regexp: /r4\d/,
|
||||||
|
againstName: true,
|
||||||
|
priority: PRIO3,
|
||||||
|
log: true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe( 'consumeFolderByRegexpExpression', () => {
|
||||||
|
// and accept priority in any order
|
||||||
|
// the last one is in effect
|
||||||
|
// and accept multiple
|
||||||
|
it.each([
|
||||||
|
// Plain cases
|
||||||
|
['for-name: /!: debug: r4\\d', PRIO1],
|
||||||
|
['for-name: /!: debug: r4\\d', PRIO1],
|
||||||
|
['/!!: for-name: debug: r4\\d', PRIO2],
|
||||||
|
['/!: debug: for-name: r4\\d', PRIO1],
|
||||||
|
['debug: for-name: /!!!: r4\\d', PRIO3],
|
||||||
|
['debug: /!: for-name: r4\\d', PRIO1],
|
||||||
|
// Cases with duplication of same
|
||||||
|
['for-name: for-name: /!: debug: r4\\d', PRIO1],
|
||||||
|
['for-name: /!: /!: debug: debug: r4\\d', PRIO1],
|
||||||
|
['/!!: for-name: /!!: debug: r4\\d', PRIO2],
|
||||||
|
['/!: debug: debug: for-name: r4\\d', PRIO1],
|
||||||
|
['debug: for-name: /!!!:/!!!: r4\\d', PRIO3],
|
||||||
|
['debug: /!: for-name: /!: r4\\d', PRIO1],
|
||||||
|
// Cases with duplication of different priority
|
||||||
|
['debug: /!!!: for-name: /!: r4\\d', PRIO1],
|
||||||
|
['debug: /!: for-name: /!!: r4\\d', PRIO2],
|
||||||
|
['debug: /!: for-name: /!!: /!!!: /!: /!!!: r4\\d', PRIO3],
|
||||||
|
])('should recognize all modifiers in >%s< of priority %s', (regexpExpr: string, prio: number) => {
|
||||||
|
const result: ConsumedFolderMatchingRegexp = consumeFolderByRegexpExpression(regexpExpr)
|
||||||
|
expect(result).toEqual(consumedTargetFolderRegexp[prio])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const txtInputPriorityGroups1: string = `
|
const txtInputPriorityGroups1: string = `
|
||||||
target-folder: /
|
target-folder: /
|
||||||
/:files
|
/:files
|
||||||
|
@ -1694,6 +1916,48 @@ describe('SortingSpecProcessor error detection and reporting', () => {
|
||||||
expect(result).not.toBeNull()
|
expect(result).not.toBeNull()
|
||||||
expect(errorsLogger).not.toHaveBeenCalled()
|
expect(errorsLogger).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
it('should recognize empty regexp of target-folder:', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: regexp:
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(1)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 27:InvalidOrEmptyFolderMatchingRegexp Invalid or empty folder regexp expression <> ${ERR_SUFFIX}`)
|
||||||
|
})
|
||||||
|
it('should recognize error in regexp of target-folder:', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: regexp: bla (
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(1)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 27:InvalidOrEmptyFolderMatchingRegexp Invalid or empty folder regexp expression <bla (> ${ERR_SUFFIX}`)
|
||||||
|
})
|
||||||
|
it('should recognize empty name in target-folder: name:', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: name:
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(1)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 26:EmptyFolderNameToMatch Empty 'target-folder: name:' value ${ERR_SUFFIX}`)
|
||||||
|
})
|
||||||
|
it('should recognize duplicate name in target-folder: name:', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: name: 123
|
||||||
|
target-folder: name: xyz
|
||||||
|
target-folder: name: 123
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(1)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 25:DuplicateByNameSortSpecForFolder Duplicate 'target-folder: name:' definition for the same name <123> ${ERR_SUFFIX}`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const txtInputTargetFolderCCC: string = `
|
const txtInputTargetFolderCCC: string = `
|
||||||
|
|
|
@ -22,10 +22,12 @@ import {
|
||||||
RomanNumberRegexStr
|
RomanNumberRegexStr
|
||||||
} from "./matchers";
|
} from "./matchers";
|
||||||
import {
|
import {
|
||||||
|
FolderMatchingRegexp,
|
||||||
FolderWildcardMatching,
|
FolderWildcardMatching,
|
||||||
MATCH_ALL_SUFFIX,
|
MATCH_ALL_SUFFIX,
|
||||||
MATCH_CHILDREN_1_SUFFIX,
|
MATCH_CHILDREN_1_SUFFIX,
|
||||||
MATCH_CHILDREN_2_SUFFIX
|
MATCH_CHILDREN_2_SUFFIX,
|
||||||
|
NO_PRIORITY
|
||||||
} from "./folder-matching-rules"
|
} from "./folder-matching-rules"
|
||||||
|
|
||||||
interface ProcessingContext {
|
interface ProcessingContext {
|
||||||
|
@ -75,13 +77,19 @@ export enum ProblemCode {
|
||||||
TooManyGroupTypePrefixes,
|
TooManyGroupTypePrefixes,
|
||||||
PriorityPrefixAfterGroupTypePrefix,
|
PriorityPrefixAfterGroupTypePrefix,
|
||||||
CombinePrefixAfterGroupTypePrefix,
|
CombinePrefixAfterGroupTypePrefix,
|
||||||
InlineRegexInPrefixAndSuffix
|
InlineRegexInPrefixAndSuffix,
|
||||||
|
DuplicateByNameSortSpecForFolder,
|
||||||
|
EmptyFolderNameToMatch,
|
||||||
|
InvalidOrEmptyFolderMatchingRegexp
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContextFreeProblems = new Set<ProblemCode>([
|
const ContextFreeProblems = new Set<ProblemCode>([
|
||||||
ProblemCode.DuplicateSortSpecForSameFolder,
|
ProblemCode.DuplicateSortSpecForSameFolder,
|
||||||
ProblemCode.DuplicateWildcardSortSpecForSameFolder,
|
ProblemCode.DuplicateWildcardSortSpecForSameFolder,
|
||||||
ProblemCode.OnlyLastCombinedGroupCanSpecifyOrder
|
ProblemCode.OnlyLastCombinedGroupCanSpecifyOrder,
|
||||||
|
ProblemCode.DuplicateByNameSortSpecForFolder,
|
||||||
|
ProblemCode.EmptyFolderNameToMatch,
|
||||||
|
ProblemCode.InvalidOrEmptyFolderMatchingRegexp
|
||||||
])
|
])
|
||||||
|
|
||||||
const ThreeDots = '...';
|
const ThreeDots = '...';
|
||||||
|
@ -157,9 +165,11 @@ enum Attribute {
|
||||||
OrderStandardObsidian
|
OrderStandardObsidian
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TargetFolderLexeme: string = 'target-folder:'
|
||||||
|
|
||||||
const AttrLexems: { [key: string]: Attribute } = {
|
const AttrLexems: { [key: string]: Attribute } = {
|
||||||
// Verbose attr names
|
// Verbose attr names
|
||||||
'target-folder:': Attribute.TargetFolder,
|
[TargetFolderLexeme]: Attribute.TargetFolder,
|
||||||
'order-asc:': Attribute.OrderAsc,
|
'order-asc:': Attribute.OrderAsc,
|
||||||
'order-desc:': Attribute.OrderDesc,
|
'order-desc:': Attribute.OrderDesc,
|
||||||
'sorting:': Attribute.OrderStandardObsidian,
|
'sorting:': Attribute.OrderStandardObsidian,
|
||||||
|
@ -203,6 +213,10 @@ const PriorityModifierPrio1Lexeme: string = '/!'
|
||||||
const PriorityModifierPrio2Lexeme: string = '/!!'
|
const PriorityModifierPrio2Lexeme: string = '/!!'
|
||||||
const PriorityModifierPrio3Lexeme: string = '/!!!'
|
const PriorityModifierPrio3Lexeme: string = '/!!!'
|
||||||
|
|
||||||
|
const PriorityModifierPrio1TargetFolderLexeme: string = '/!:'
|
||||||
|
const PriorityModifierPrio2TargetFolderLexeme: string = '/!!:'
|
||||||
|
const PriorityModifierPrio3TargetFolderLexeme: string = '/!!!:'
|
||||||
|
|
||||||
const PRIO_1: number = 1
|
const PRIO_1: number = 1
|
||||||
const PRIO_2: number = 2
|
const PRIO_2: number = 2
|
||||||
const PRIO_3: number = 3
|
const PRIO_3: number = 3
|
||||||
|
@ -213,6 +227,12 @@ const SortingGroupPriorityPrefixes: { [key: string]: number } = {
|
||||||
[PriorityModifierPrio3Lexeme]: PRIO_3
|
[PriorityModifierPrio3Lexeme]: PRIO_3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TargetFolderRegexpPriorityPrefixes: { [key: string]: number } = {
|
||||||
|
[PriorityModifierPrio1TargetFolderLexeme]: PRIO_1,
|
||||||
|
[PriorityModifierPrio2TargetFolderLexeme]: PRIO_2,
|
||||||
|
[PriorityModifierPrio3TargetFolderLexeme]: PRIO_3
|
||||||
|
}
|
||||||
|
|
||||||
const CombineGroupLexeme: string = '/+'
|
const CombineGroupLexeme: string = '/+'
|
||||||
|
|
||||||
const CombiningGroupPrefixes: Array<string> = [
|
const CombiningGroupPrefixes: Array<string> = [
|
||||||
|
@ -491,15 +511,35 @@ export const convertInlineRegexSymbolsAndEscapeTheRest = (s: string): RegexAsStr
|
||||||
return regexAsString.join('')
|
return regexAsString.join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MatchFolderNameLexeme: string = 'name:'
|
||||||
|
export const MatchFolderByRegexpLexeme: string = 'regexp:'
|
||||||
|
export const RegexpAgainstFolderName: string = 'for-name:'
|
||||||
|
export const DebugFolderRegexMatchesLexeme: string = 'debug:'
|
||||||
|
|
||||||
|
type FolderPath = string
|
||||||
|
type FolderName = string
|
||||||
|
|
||||||
export interface FolderPathToSortSpecMap {
|
export interface FolderPathToSortSpecMap {
|
||||||
[key: string]: CustomSortSpec
|
[key: FolderPath]: CustomSortSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FolderNameToSortSpecMap {
|
||||||
|
[key: FolderName]: CustomSortSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SortSpecsCollection {
|
export interface SortSpecsCollection {
|
||||||
sortSpecByPath: FolderPathToSortSpecMap
|
sortSpecByPath: FolderPathToSortSpecMap
|
||||||
|
sortSpecByName: FolderNameToSortSpecMap
|
||||||
sortSpecByWildcard?: FolderWildcardMatching<CustomSortSpec>
|
sortSpecByWildcard?: FolderWildcardMatching<CustomSortSpec>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const newSortSpecsCollection = (): SortSpecsCollection => {
|
||||||
|
return {
|
||||||
|
sortSpecByPath: {},
|
||||||
|
sortSpecByName: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface AdjacencyInfo {
|
interface AdjacencyInfo {
|
||||||
noPrefix: boolean,
|
noPrefix: boolean,
|
||||||
noSuffix: boolean
|
noSuffix: boolean
|
||||||
|
@ -524,32 +564,95 @@ enum WildcardPriority {
|
||||||
MATCH_ALL
|
MATCH_ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
const stripWildcardPatternSuffix = (path: string): [path: string, priority: number] => {
|
const stripWildcardPatternSuffix = (path: string): {path: string, detectedWildcardPriority: number} => {
|
||||||
if (path.endsWith(MATCH_ALL_SUFFIX)) {
|
if (path.endsWith(MATCH_ALL_SUFFIX)) {
|
||||||
path = path.slice(0, -MATCH_ALL_SUFFIX.length)
|
path = path.slice(0, -MATCH_ALL_SUFFIX.length)
|
||||||
return [
|
return {
|
||||||
path.length > 0 ? path : '/',
|
path: path.length > 0 ? path : '/',
|
||||||
WildcardPriority.MATCH_ALL
|
detectedWildcardPriority: WildcardPriority.MATCH_ALL
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
if (path.endsWith(MATCH_CHILDREN_1_SUFFIX)) {
|
if (path.endsWith(MATCH_CHILDREN_1_SUFFIX)) {
|
||||||
path = path.slice(0, -MATCH_CHILDREN_1_SUFFIX.length)
|
path = path.slice(0, -MATCH_CHILDREN_1_SUFFIX.length)
|
||||||
return [
|
return {
|
||||||
path.length > 0 ? path : '/',
|
path: path.length > 0 ? path : '/',
|
||||||
WildcardPriority.MATCH_CHILDREN,
|
detectedWildcardPriority: WildcardPriority.MATCH_CHILDREN,
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
if (path.endsWith(MATCH_CHILDREN_2_SUFFIX)) {
|
if (path.endsWith(MATCH_CHILDREN_2_SUFFIX)) {
|
||||||
path = path.slice(0, -MATCH_CHILDREN_2_SUFFIX.length)
|
path = path.slice(0, -MATCH_CHILDREN_2_SUFFIX.length)
|
||||||
return [
|
return {
|
||||||
path.length > 0 ? path : '/',
|
path: path.length > 0 ? path : '/',
|
||||||
WildcardPriority.MATCH_CHILDREN
|
detectedWildcardPriority: WildcardPriority.MATCH_CHILDREN
|
||||||
]
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path: path,
|
||||||
|
detectedWildcardPriority: WildcardPriority.NO_WILDCARD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eatPrefixIfPresent = (expression: string, prefix: string, onDetected: () => void): string => {
|
||||||
|
const detected: boolean = expression.startsWith(prefix)
|
||||||
|
if (detected) {
|
||||||
|
onDetected()
|
||||||
|
return expression.substring(prefix.length).trim()
|
||||||
|
} else {
|
||||||
|
return expression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConsumedFolderMatchingRegexp {
|
||||||
|
regexp: RegExp
|
||||||
|
againstName: boolean
|
||||||
|
priority: number | undefined
|
||||||
|
log: boolean | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const consumeFolderByRegexpExpression = (expression: string): ConsumedFolderMatchingRegexp => {
|
||||||
|
let againstName: boolean = false
|
||||||
|
let priority: number | undefined
|
||||||
|
let logMatches: boolean | undefined
|
||||||
|
|
||||||
|
let nextRoundNeeded: boolean
|
||||||
|
|
||||||
|
do {
|
||||||
|
nextRoundNeeded = false
|
||||||
|
|
||||||
|
expression = eatPrefixIfPresent(expression, RegexpAgainstFolderName, () => {
|
||||||
|
againstName = true
|
||||||
|
nextRoundNeeded = true
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const priorityPrefix of Object.keys(TargetFolderRegexpPriorityPrefixes)) {
|
||||||
|
let doBreak: boolean = false
|
||||||
|
expression = eatPrefixIfPresent(expression, priorityPrefix, () => {
|
||||||
|
priority = TargetFolderRegexpPriorityPrefixes[priorityPrefix]
|
||||||
|
nextRoundNeeded = true
|
||||||
|
doBreak = true
|
||||||
|
})
|
||||||
|
if (doBreak) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expression = eatPrefixIfPresent(expression, DebugFolderRegexMatchesLexeme, () => {
|
||||||
|
logMatches = true
|
||||||
|
nextRoundNeeded = true
|
||||||
|
})
|
||||||
|
} while (nextRoundNeeded)
|
||||||
|
|
||||||
|
// do not allow empty regexp
|
||||||
|
if (!expression || expression.trim() === '') {
|
||||||
|
throw new Error('Empty regexp')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
regexp: new RegExp(expression),
|
||||||
|
againstName: againstName,
|
||||||
|
priority: priority === undefined ? NO_PRIORITY : priority,
|
||||||
|
log: !!logMatches
|
||||||
}
|
}
|
||||||
return [
|
|
||||||
path,
|
|
||||||
WildcardPriority.NO_WILDCARD
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplistic
|
// Simplistic
|
||||||
|
@ -641,12 +744,52 @@ export class SortingSpecProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortspecByWildcard: FolderWildcardMatching<CustomSortSpec> | undefined
|
let sortspecByName: FolderNameToSortSpecMap | undefined
|
||||||
for (let spec of this.ctx.specs) {
|
for (let spec of this.ctx.specs) {
|
||||||
// Consume the folder paths ending with wildcard specs
|
// Consume the folder names prefixed by the designated lexeme
|
||||||
for (let idx = 0; idx<spec.targetFoldersPaths.length; idx++) {
|
for (let idx = 0; idx<spec.targetFoldersPaths.length; idx++) {
|
||||||
const path = spec.targetFoldersPaths[idx]
|
const path = spec.targetFoldersPaths[idx]
|
||||||
if (endsWithWildcardPatternSuffix(path)) {
|
if (path.startsWith(MatchFolderNameLexeme)) {
|
||||||
|
const folderNameToMatch: string = path.substring(MatchFolderNameLexeme.length).trim()
|
||||||
|
if (folderNameToMatch === '') {
|
||||||
|
this.problem(ProblemCode.EmptyFolderNameToMatch,
|
||||||
|
`Empty '${TargetFolderLexeme} ${MatchFolderNameLexeme}' value` )
|
||||||
|
return null // Failure - not allow duplicate by folderNameToMatch specs for the same folder folderNameToMatch
|
||||||
|
}
|
||||||
|
sortspecByName = sortspecByName ?? {}
|
||||||
|
if (sortspecByName[folderNameToMatch]) {
|
||||||
|
this.problem(ProblemCode.DuplicateByNameSortSpecForFolder,
|
||||||
|
`Duplicate '${TargetFolderLexeme} ${MatchFolderNameLexeme}' definition for the same name <${folderNameToMatch}>` )
|
||||||
|
return null // Failure - not allow duplicate by folderNameToMatch specs for the same folder folderNameToMatch
|
||||||
|
} else {
|
||||||
|
sortspecByName[folderNameToMatch] = spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortspecByName) {
|
||||||
|
collection = collection ?? newSortSpecsCollection()
|
||||||
|
collection.sortSpecByName = sortspecByName
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortspecByWildcard: FolderWildcardMatching<CustomSortSpec> | undefined
|
||||||
|
for (let spec of this.ctx.specs) {
|
||||||
|
// Consume the folder paths ending with wildcard specs or regexp-based
|
||||||
|
for (let idx = 0; idx<spec.targetFoldersPaths.length; idx++) {
|
||||||
|
const path = spec.targetFoldersPaths[idx]
|
||||||
|
if (path.startsWith(MatchFolderByRegexpLexeme)) {
|
||||||
|
sortspecByWildcard = sortspecByWildcard ?? new FolderWildcardMatching<CustomSortSpec>()
|
||||||
|
const folderByRegexpExpression: string = path.substring(MatchFolderByRegexpLexeme.length).trim()
|
||||||
|
try {
|
||||||
|
const r: ConsumedFolderMatchingRegexp = consumeFolderByRegexpExpression(folderByRegexpExpression)
|
||||||
|
sortspecByWildcard.addRegexpDefinition(r.regexp, r.againstName, r.priority, r.log, spec)
|
||||||
|
} catch (e) {
|
||||||
|
this.problem(ProblemCode.InvalidOrEmptyFolderMatchingRegexp,
|
||||||
|
`Invalid or empty folder regexp expression <${folderByRegexpExpression}>`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else if (endsWithWildcardPatternSuffix(path)) {
|
||||||
sortspecByWildcard = sortspecByWildcard ?? new FolderWildcardMatching<CustomSortSpec>()
|
sortspecByWildcard = sortspecByWildcard ?? new FolderWildcardMatching<CustomSortSpec>()
|
||||||
const ruleAdded = sortspecByWildcard.addWildcardDefinition(path, spec)
|
const ruleAdded = sortspecByWildcard.addWildcardDefinition(path, spec)
|
||||||
if (ruleAdded?.errorMsg) {
|
if (ruleAdded?.errorMsg) {
|
||||||
|
@ -658,31 +801,31 @@ export class SortingSpecProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortspecByWildcard) {
|
if (sortspecByWildcard) {
|
||||||
collection = collection ?? { sortSpecByPath:{} }
|
collection = collection ?? newSortSpecsCollection()
|
||||||
collection.sortSpecByWildcard = sortspecByWildcard
|
collection.sortSpecByWildcard = sortspecByWildcard
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let spec of this.ctx.specs) {
|
for (let spec of this.ctx.specs) {
|
||||||
for (let idx = 0; idx < spec.targetFoldersPaths.length; idx++) {
|
for (let idx = 0; idx < spec.targetFoldersPaths.length; idx++) {
|
||||||
const originalPath = spec.targetFoldersPaths[idx]
|
const originalPath = spec.targetFoldersPaths[idx]
|
||||||
collection = collection ?? { sortSpecByPath: {} }
|
if (!originalPath.startsWith(MatchFolderNameLexeme) && !originalPath.startsWith(MatchFolderByRegexpLexeme)) {
|
||||||
let detectedWildcardPriority: WildcardPriority
|
collection = collection ?? newSortSpecsCollection()
|
||||||
let path: string
|
const {path, detectedWildcardPriority} = stripWildcardPatternSuffix(originalPath)
|
||||||
[path, detectedWildcardPriority] = stripWildcardPatternSuffix(originalPath)
|
let storeTheSpec: boolean = true
|
||||||
let storeTheSpec: boolean = true
|
const preexistingSortSpecPriority: WildcardPriority = this.pathMatchPriorityForPath[path]
|
||||||
const preexistingSortSpecPriority: WildcardPriority = this.pathMatchPriorityForPath[path]
|
if (preexistingSortSpecPriority) {
|
||||||
if (preexistingSortSpecPriority) {
|
if (preexistingSortSpecPriority === WildcardPriority.NO_WILDCARD && detectedWildcardPriority === WildcardPriority.NO_WILDCARD) {
|
||||||
if (preexistingSortSpecPriority === WildcardPriority.NO_WILDCARD && detectedWildcardPriority === WildcardPriority.NO_WILDCARD) {
|
this.problem(ProblemCode.DuplicateSortSpecForSameFolder, `Duplicate sorting spec for folder ${path}`)
|
||||||
this.problem(ProblemCode.DuplicateSortSpecForSameFolder, `Duplicate sorting spec for folder ${path}`)
|
return null // Failure - not allow duplicate specs for the same no-wildcard folder path
|
||||||
return null // Failure - not allow duplicate specs for the same no-wildcard folder path
|
} else if (detectedWildcardPriority >= preexistingSortSpecPriority) {
|
||||||
} else if (detectedWildcardPriority >= preexistingSortSpecPriority) {
|
// Ignore lower priority rule
|
||||||
// Ignore lower priority rule
|
storeTheSpec = false
|
||||||
storeTheSpec = false
|
}
|
||||||
|
}
|
||||||
|
if (storeTheSpec) {
|
||||||
|
collection.sortSpecByPath[path] = spec
|
||||||
|
this.pathMatchPriorityForPath[path] = detectedWildcardPriority
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (storeTheSpec) {
|
|
||||||
collection.sortSpecByPath[path] = spec
|
|
||||||
this.pathMatchPriorityForPath[path] = detectedWildcardPriority
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,15 +32,13 @@ interface CustomSortPluginSettings {
|
||||||
suspended: boolean
|
suspended: boolean
|
||||||
statusBarEntryEnabled: boolean
|
statusBarEntryEnabled: boolean
|
||||||
notificationsEnabled: boolean
|
notificationsEnabled: boolean
|
||||||
allowRegexpInTargetFolder: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: CustomSortPluginSettings = {
|
const DEFAULT_SETTINGS: CustomSortPluginSettings = {
|
||||||
additionalSortspecFile: '',
|
additionalSortspecFile: '',
|
||||||
suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install
|
suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install
|
||||||
statusBarEntryEnabled: true,
|
statusBarEntryEnabled: true,
|
||||||
notificationsEnabled: true,
|
notificationsEnabled: true
|
||||||
allowRegexpInTargetFolder: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
|
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
|
||||||
|
@ -323,15 +321,16 @@ export default class CustomSortPlugin extends Plugin {
|
||||||
// if custom sort is not specified, use the UI-selected
|
// if custom sort is not specified, use the UI-selected
|
||||||
const folder: TFolder = this.file
|
const folder: TFolder = this.file
|
||||||
let sortSpec: CustomSortSpec | null | undefined = plugin.sortSpecCache?.sortSpecByPath[folder.path]
|
let sortSpec: CustomSortSpec | null | undefined = plugin.sortSpecCache?.sortSpecByPath[folder.path]
|
||||||
|
sortSpec = sortSpec ?? plugin.sortSpecCache?.sortSpecByName[folder.name]
|
||||||
if (sortSpec) {
|
if (sortSpec) {
|
||||||
if (sortSpec.defaultOrder === CustomSortOrder.standardObsidian) {
|
if (sortSpec.defaultOrder === CustomSortOrder.standardObsidian) {
|
||||||
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
|
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
|
||||||
}
|
}
|
||||||
} else if (plugin.sortSpecCache?.sortSpecByWildcard) {
|
} else if (plugin.sortSpecCache?.sortSpecByWildcard) {
|
||||||
// when no sorting spec found directly by folder path, check for wildcard-based match
|
// when no sorting spec found directly by folder path, check for wildcard-based match
|
||||||
sortSpec = plugin.sortSpecCache?.sortSpecByWildcard.folderMatch(folder.path)
|
sortSpec = plugin.sortSpecCache?.sortSpecByWildcard.folderMatch(folder.path, folder.name)
|
||||||
if (sortSpec?.defaultOrder === CustomSortOrder.standardObsidian) {
|
if (sortSpec?.defaultOrder === CustomSortOrder.standardObsidian) {
|
||||||
sortSpec = null // A folder subtree can be also explicitly excluded from custom sorting plugin
|
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sortSpec) {
|
if (sortSpec) {
|
||||||
|
|
Loading…
Reference in New Issue