Refactoring of internal processing context plus support for implicit sorting specs

- !!! NO UNIT TESTS ADDED - remember to do it
This commit is contained in:
SebastianMC 2023-08-24 11:18:22 +02:00
parent 45f5918598
commit 3cc58f69b9
7 changed files with 153 additions and 131 deletions

View File

@ -79,10 +79,7 @@ export interface CustomSortSpec {
outsidersFoldersGroupIdx?: number
itemsToHide?: Set<string>
priorityOrder?: Array<number> // Indexes of groups in evaluation order
// For internal transient use
plugin?: Plugin // to hand over the access to App instance to the sorting engine
_mCache?: MetadataCache
implicit?: boolean // spec applied automatically (e.g. auto integration with a plugin)
}
export const DEFAULT_METADATA_FIELD_FOR_SORTING: string = 'sort-index-value'

View File

@ -9,7 +9,7 @@ import {
sorterByMetadataField,
SorterFn,
getSorterFnFor,
Sorters
ProcessingContext
} from './custom-sort';
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, RegExpSpec} from './custom-sort-types';
import {CompoundDashNumberNormalizerFn, CompoundDotRomanNumberNormalizerFn} from "./sorting-spec-processor";
@ -744,7 +744,9 @@ describe('determineSortingGroup', () => {
type: CustomSortGroupType.HasMetadataField,
withMetadataFieldName: "metadataField1",
exactPrefix: 'Ref'
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -760,7 +762,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -781,7 +783,9 @@ describe('determineSortingGroup', () => {
type: CustomSortGroupType.HasMetadataField,
withMetadataFieldName: "metadataField1",
exactPrefix: 'Ref'
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -797,7 +801,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -818,7 +822,9 @@ describe('determineSortingGroup', () => {
type: CustomSortGroupType.HasMetadataField,
withMetadataFieldName: "metadataField1",
exactPrefix: 'Ref'
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -834,7 +840,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -855,7 +861,9 @@ describe('determineSortingGroup', () => {
type: CustomSortGroupType.HasMetadataField,
withMetadataFieldName: "metadataField1",
exactPrefix: 'Ref'
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -871,7 +879,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(folder, sortSpec)
const result = determineSortingGroup(folder, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -904,7 +912,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(file, sortSpec, {
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -935,7 +943,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(file, sortSpec, {
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -966,7 +974,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1005,7 +1013,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1044,7 +1052,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1086,7 +1094,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(file, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1121,7 +1129,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(file, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1155,7 +1163,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(file, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1190,7 +1198,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(file, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1221,7 +1229,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1263,7 +1271,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1308,7 +1316,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1351,7 +1359,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1397,7 +1405,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1440,7 +1448,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1486,7 +1494,7 @@ describe('determineSortingGroup', () => {
// when
const result = determineSortingGroup(folder, sortSpec, {
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
})
} as ProcessingContext)
// then
expect(result).toEqual({
@ -1519,7 +1527,9 @@ describe('determineSortingGroup', () => {
byMetadataField: 'metadata-field-for-sorting',
exactPrefix: 'Ref',
order: CustomSortOrder.byMetadataFieldAlphabetical
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1535,7 +1545,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -1558,7 +1568,9 @@ describe('determineSortingGroup', () => {
byMetadataField: 'metadata-field-for-sorting',
exactPrefix: 'Ref',
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1574,7 +1586,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -1597,7 +1609,9 @@ describe('determineSortingGroup', () => {
byMetadataField: 'metadata-field-for-sorting',
exactPrefix: 'Ref',
order: CustomSortOrder.byMetadataFieldTrueAlphabetical
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1613,7 +1627,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -1636,7 +1650,9 @@ describe('determineSortingGroup', () => {
byMetadataField: 'metadata-field-for-sorting',
exactPrefix: 'Ref',
order: CustomSortOrder.byMetadataFieldTrueAlphabeticalReverse
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1652,7 +1668,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -1675,7 +1691,9 @@ describe('determineSortingGroup', () => {
exactPrefix: 'Ref',
byMetadataField: 'metadata-field-for-sorting',
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1691,7 +1709,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(folder, sortSpec)
const result = determineSortingGroup(folder, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -1715,6 +1733,10 @@ describe('determineSortingGroup', () => {
exactPrefix: 'Ref',
order: CustomSortOrder.byMetadataFieldAlphabetical
}],
defaultOrder: CustomSortOrder.byMetadataFieldAlphabeticalReverse,
byMetadataField: 'metadata-field-for-sorting-specified-on-target-folder'
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1726,13 +1748,11 @@ describe('determineSortingGroup', () => {
}
}[path]
}
} as MetadataCache,
defaultOrder: CustomSortOrder.byMetadataFieldAlphabeticalReverse,
byMetadataField: 'metadata-field-for-sorting-specified-on-target-folder',
} as MetadataCache
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -1754,7 +1774,9 @@ describe('determineSortingGroup', () => {
type: CustomSortGroupType.HasMetadataField,
order: CustomSortOrder.byMetadataFieldAlphabetical,
withMetadataFieldName: 'field-used-with-with-metadata-syntax'
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1770,7 +1792,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -1792,7 +1814,9 @@ describe('determineSortingGroup', () => {
type: CustomSortGroupType.ExactPrefix,
exactPrefix: 'Ref',
order: CustomSortOrder.byMetadataFieldAlphabetical
}],
}]
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
@ -1808,7 +1832,7 @@ describe('determineSortingGroup', () => {
}
// when
const result = determineSortingGroup(file, sortSpec)
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
// then
expect(result).toEqual({
@ -2122,7 +2146,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
const itemB: Partial<FolderItemForSorting> = {
metadataFieldValue: 'B'
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
@ -2142,7 +2166,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
metadataFieldValue: 'Aaa',
sortString: 'a123'
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
@ -2163,7 +2187,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
const itemB: Partial<FolderItemForSorting> = {
sortString: 'n123'
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
@ -2181,7 +2205,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
const itemB: Partial<FolderItemForSorting> = {
sortString: 'ccc '
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
@ -2204,7 +2228,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
const itemB: Partial<FolderItemForSorting> = {
metadataFieldValue: 'B'
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
@ -2224,7 +2248,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
metadataFieldValue: 'Aaa',
sortString: 'a123'
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
@ -2236,25 +2260,6 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
expect(result2).toBe(SORT_FIRST_GOES_LATER)
expect(result3).toBe(SORT_ITEMS_ARE_EQUAL)
})
it('should put the item with metadata below the second one w/o metadata (this is reverse order)', () => {
// given
const itemA: Partial<FolderItemForSorting> = {
metadataFieldValue: '15',
sortString: 'n123'
}
const itemB: Partial<FolderItemForSorting> = {
sortString: 'n123'
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
const result2: number = sorter(itemB as FolderItemForSorting, itemA as FolderItemForSorting)
// then
expect(result1).toBe(SORT_FIRST_GOES_LATER)
expect(result2).toBe(SORT_FIRST_GOES_EARLIER)
})
it('should put the item with metadata later if the second one has no metadata (reverse order)', () => {
// given
const itemA: Partial<FolderItemForSorting> = {
@ -2264,7 +2269,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
const itemB: Partial<FolderItemForSorting> = {
sortString: 'n123'
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
@ -2282,7 +2287,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
const itemB: Partial<FolderItemForSorting> = {
sortString: 'ccc '
}
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
// when
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)

View File

@ -3,6 +3,7 @@ import {
CommunityPlugin,
FrontMatterCache,
InstalledPlugin,
MetadataCache,
requireApiVersion,
TAbstractFile,
TFile,
@ -39,6 +40,15 @@ import {
} from "./macros";
import {Obj} from "tern";
export interface ProcessingContext {
// For internal transient use
plugin?: Plugin // to hand over the access to App instance to the sorting engine
_mCache?: MetadataCache
starredPluginInstance?: Starred_PluginInstance
iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance
}
let CollatorCompare = new Intl.Collator(undefined, {
usage: "sort",
sensitivity: "base",
@ -96,7 +106,7 @@ export const sorterByMetadataField:(reverseOrder?: boolean, trueAlphabetical?: b
}
}
export let Sorters: { [key in CustomSortOrder]: SorterFn } = {
let Sorters: { [key in CustomSortOrder]: SorterFn } = {
[CustomSortOrder.alphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
[CustomSortOrder.trueAlphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortString, b.sortString),
[CustomSortOrder.alphabeticalReverse]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(b.sortString, a.sortString),
@ -236,12 +246,7 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string):
return [false, undefined, undefined]
}
export interface Context {
starredPluginInstance?: Starred_PluginInstance
iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance
}
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: Context): FolderItemForSorting {
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: ProcessingContext): FolderItemForSorting {
let groupIdx: number
let determined: boolean = false
let matchedGroup: string | null | undefined
@ -324,10 +329,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
break
case CustomSortGroupType.HasMetadataField:
if (group.withMetadataFieldName) {
if (spec._mCache) {
if (ctx?._mCache) {
// For folders - scan metadata of 'folder note'
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
const frontMatterCache: FrontMatterCache | undefined = spec._mCache.getCache(notePathToScan)?.frontmatter
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
const hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
if (hasMetadata) {
@ -417,10 +422,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
metadataFieldName = DEFAULT_METADATA_FIELD_FOR_SORTING
}
if (metadataFieldName) {
if (spec._mCache) {
if (ctx?._mCache) {
// For folders - scan metadata of 'folder note'
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
const frontMatterCache: FrontMatterCache | undefined = spec._mCache.getCache(notePathToScan)?.frontmatter
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
metadataValueToSortBy = frontMatterCache?.[metadataFieldName]
}
}
@ -496,11 +501,8 @@ export const determineFolderDatesIfNeeded = (folderItems: Array<FolderItemForSor
})
}
export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]) {
export const folderSort = function (sortingSpec: CustomSortSpec, ctx: ProcessingContext) {
let fileExplorer = this.fileExplorer
sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache
const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin()
const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance | undefined = getIconFolderPlugin()
// shallow copy of groups
sortingSpec.groupsShadow = sortingSpec.groups?.map((group) => Object.assign({} as CustomSortGroup, group))
@ -516,10 +518,7 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
:
this.file.children)
.map((entry: TFile | TFolder) => {
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, {
starredPluginInstance: starredPluginInstance,
iconFolderPluginInstance: iconFolderPluginInstance
})
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, ctx)
return itemForSorting
})
@ -538,8 +537,4 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
} else {
this.children = items;
}
// release risky references
sortingSpec._mCache = undefined
sortingSpec.plugin = undefined
};

View File

@ -2,8 +2,10 @@ import {FolderWildcardMatching} from './folder-matching-rules'
type SortingSpec = string
const checkIfImplicitSpec = (s: SortingSpec) => false
const createMockMatcherRichVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
let p: string
p = '/...'; matcher.addWildcardDefinition(p, `00 ${p}`)
p = '/*'; matcher.addWildcardDefinition(p, `0 ${p}`)
@ -19,25 +21,25 @@ const PRIO2 = 2
const PRIO3 = 3
const createMockMatcherSimplestVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/Reviews/daily/*', '/Reviews/daily/*')
return matcher
}
const createMockMatcherRootOnlyVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/...', '/...')
return matcher
}
const createMockMatcherRootOnlyDeepVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/*', '/*')
return matcher
}
const createMockMatcherSimpleVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/Reviews/daily/*', '/Reviews/daily/*')
matcher.addWildcardDefinition('/Reviews/daily/...', '/Reviews/daily/...')
return matcher
@ -108,21 +110,21 @@ describe('folderMatch', () => {
expect(match3).toBe('/Reviews/daily/...')
})
it('should detect duplicate match children definitions for same path', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('Archive/2020/...', 'First occurrence')
const result = matcher.addWildcardDefinition('/Archive/2020/.../', 'Duplicate')
expect(result).toEqual({errorMsg: "Duplicate wildcard '...' specification for /Archive/2020/.../"})
})
it('should detect duplicate match all definitions for same path', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/Archive/2019/*', 'First occurrence')
const result = matcher.addWildcardDefinition('Archive/2019/*', 'Duplicate')
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()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -134,7 +136,7 @@ describe('folderMatch', () => {
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()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -146,7 +148,7 @@ describe('folderMatch', () => {
expect(match2).toBe('r2')
})
it('regexp-match by path works (order of regexp doesn\'t matter) case A', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`)
matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`)
matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -158,7 +160,7 @@ describe('folderMatch', () => {
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()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`)
matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`)
matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -170,7 +172,7 @@ describe('folderMatch', () => {
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()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -179,7 +181,7 @@ describe('folderMatch', () => {
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()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -188,7 +190,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1')
})
it('regexp-match priorities - order of definitions irrelevant - unique priorities - case A', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 3, false, `r1p3`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
@ -198,7 +200,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3')
})
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^freq\/daily$/, false, undefined, false, `r4pNone`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
@ -208,7 +210,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3')
})
it('regexp-match priorities - order of definitions irrelevant - duplicate priorities - case A', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3a`)
matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3b`)
matcher.addRegexpDefinition(/^daily$/, true, 2, false, `r2p2a`)
@ -221,7 +223,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3b')
})
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^freq\/daily$/, false, undefined, false, `r4pNone`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
@ -231,14 +233,14 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3')
})
it('regexp-match - edge case of matching the root folder - match by path', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
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()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
// Tricky regexp which can return zero length matches
matcher.addRegexpDefinition(/.*/, true, undefined, false, `r1`)
matcher.addWildcardDefinition('/*', `w1`)
@ -247,7 +249,7 @@ describe('folderMatch', () => {
expect(match).toBe('w1')
})
it('regexp-match - edge case of no match when only regexp rules present', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
// 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

View File

@ -35,6 +35,8 @@ export interface AddingWildcardFailure {
errorMsg: string
}
export type CheckIfImplicitSpec<SortingSpec> = (s: SortingSpec) => boolean
export class FolderWildcardMatching<SortingSpec> {
// mimics the structure of folders, so for example tree.matchAll contains the matchAll flag for the root '/'
@ -44,6 +46,9 @@ export class FolderWildcardMatching<SortingSpec> {
regexps: Array<FolderMatchingRegexp<SortingSpec>>
constructor(private checkIfImplicitSpec: CheckIfImplicitSpec<SortingSpec>) {
}
// cache
determinedWildcardRules: { [key: string]: DeterminedSortingSpec<SortingSpec> } = {}
@ -68,13 +73,13 @@ export class FolderWildcardMatching<SortingSpec> {
}
})
if (lastComponent === MATCH_CHILDREN_PATH_TOKEN) {
if (leafNode.matchChildren) {
if (leafNode.matchChildren && !this.checkIfImplicitSpec(leafNode.matchChildren)) {
return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`}
} else {
leafNode.matchChildren = rule
}
} else { // Implicitly: MATCH_ALL_PATH_TOKEN
if (leafNode.matchAll) {
if (leafNode.matchAll && !this.checkIfImplicitSpec(leafNode.matchAll)) {
return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`}
} else {
leafNode.matchAll = rule

View File

@ -1677,6 +1677,11 @@ const txtInputErrorPriorityEmptyPattern: string = `
`
const txtInputEmptySpec: string = ``
const txtInputOnlyCommentsSpec: string = `
// Some comment
// Another comment below empty line
`
describe('SortingSpecProcessor error detection and reporting', () => {
let processor: SortingSpecProcessor;
@ -1915,7 +1920,7 @@ describe('SortingSpecProcessor error detection and reporting', () => {
`${ERR_PREFIX} 22:PriorityPrefixAfterGroupTypePrefix Priority prefix must be used before sorting group type indicator ${ERR_SUFFIX_IN_LINE(2)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/folders /+ /! Hello'))
})
it('should recognize error: combine prefix after sorting group type prefixe', () => {
it('should recognize error: combine prefix after sorting group type prefix', () => {
const inputTxtArr: Array<string> = `
/folders /+ Hello
`.replace(/\t/gi, '').split('\n')
@ -1932,6 +1937,12 @@ describe('SortingSpecProcessor error detection and reporting', () => {
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(0)
})
it('should recognize empty spec', () => {
const inputTxtArr: Array<string> = txtInputOnlyCommentsSpec.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(0)
})
it.each([
'% \\.d+...',
'% ...\\d+',

View File

@ -37,6 +37,7 @@ interface ProcessingContext {
specs: Array<CustomSortSpec>
currentSpec?: CustomSortSpec
currentSpecGroup?: CustomSortGroup
implicitSpec?: boolean
// Support for specific conditions (intentionally not generic approach)
previousValidEntryWasTargetFolderAttr?: boolean // Entry in previous non-empty valid line
@ -577,7 +578,7 @@ const ensureCollectionHasSortSpecByName = (collection?: SortSpecsCollection | nu
const ensureCollectionHasSortSpecByWildcard = (collection?: SortSpecsCollection | null) => {
collection = collection ?? {}
if (!collection.sortSpecByWildcard) {
collection.sortSpecByWildcard = new FolderWildcardMatching<CustomSortSpec>()
collection.sortSpecByWildcard = new FolderWildcardMatching<CustomSortSpec>((spec: CustomSortSpec) => !!spec.implicit)
}
return collection
}
@ -602,35 +603,38 @@ const endsWithWildcardPatternSuffix = (path: string): boolean => {
enum WildcardPriority {
NO_WILDCARD = 1,
NO_WILDCARD_IMPLICIT,
MATCH_CHILDREN,
MATCH_ALL
MATCH_CHILDREN_IMPLICIT,
MATCH_ALL,
MATCH_ALL_IMPLICIT
}
const stripWildcardPatternSuffix = (path: string): {path: string, detectedWildcardPriority: number} => {
const stripWildcardPatternSuffix = (path: string, ofImplicitSpec: boolean): {path: string, detectedWildcardPriority: number} => {
if (path.endsWith(MATCH_ALL_SUFFIX)) {
path = path.slice(0, -MATCH_ALL_SUFFIX.length)
return {
path: path.length > 0 ? path : '/',
detectedWildcardPriority: WildcardPriority.MATCH_ALL
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.MATCH_ALL_IMPLICIT : WildcardPriority.MATCH_ALL
}
}
if (path.endsWith(MATCH_CHILDREN_1_SUFFIX)) {
path = path.slice(0, -MATCH_CHILDREN_1_SUFFIX.length)
return {
path: path.length > 0 ? path : '/',
detectedWildcardPriority: WildcardPriority.MATCH_CHILDREN,
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.MATCH_CHILDREN_IMPLICIT : WildcardPriority.MATCH_CHILDREN
}
}
if (path.endsWith(MATCH_CHILDREN_2_SUFFIX)) {
path = path.slice(0, -MATCH_CHILDREN_2_SUFFIX.length)
return {
path: path.length > 0 ? path : '/',
detectedWildcardPriority: WildcardPriority.MATCH_CHILDREN
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.MATCH_CHILDREN_IMPLICIT : WildcardPriority.MATCH_CHILDREN
}
}
return {
path: path,
detectedWildcardPriority: WildcardPriority.NO_WILDCARD
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.NO_WILDCARD_IMPLICIT : WildcardPriority.NO_WILDCARD
}
}
@ -727,12 +731,14 @@ export class SortingSpecProcessor {
parseSortSpecFromText(text: Array<string>,
folderPath: string,
sortingSpecFileName: string,
collection?: SortSpecsCollection | null
collection?: SortSpecsCollection | null,
implicitSpec?: boolean
): SortSpecsCollection | null | undefined {
// reset / init processing state after potential previous invocation
this.ctx = {
folderPath: folderPath, // location of the sorting spec file
specs: []
specs: [],
implicitSpec: implicitSpec
};
this.currentEntryLine = null
this.currentEntryLineIdx = null
@ -839,7 +845,7 @@ export class SortingSpecProcessor {
for (let idx = 0; idx < spec.targetFoldersPaths.length; idx++) {
const originalPath = spec.targetFoldersPaths[idx]
if (!originalPath.startsWith(MatchFolderNameLexeme) && !originalPath.startsWith(MatchFolderByRegexpLexeme)) {
const {path, detectedWildcardPriority} = stripWildcardPatternSuffix(originalPath)
const {path, detectedWildcardPriority} = stripWildcardPatternSuffix(originalPath, !!spec.implicit)
let storeTheSpec: boolean = true
const preexistingSortSpecPriority: WildcardPriority = this.pathMatchPriorityForPath[path]
if (preexistingSortSpecPriority) {
@ -1446,7 +1452,8 @@ export class SortingSpecProcessor {
private putNewSpecForNewTargetFolder(folderPath?: string): CustomSortSpec {
const newSpec: CustomSortSpec = {
targetFoldersPaths: [folderPath ?? this.ctx.folderPath],
groups: []
groups: [],
implicit: this.ctx.implicitSpec
}
this.ctx.specs.push(newSpec);