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

View File

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

View File

@ -3,6 +3,7 @@ import {
CommunityPlugin, CommunityPlugin,
FrontMatterCache, FrontMatterCache,
InstalledPlugin, InstalledPlugin,
MetadataCache,
requireApiVersion, requireApiVersion,
TAbstractFile, TAbstractFile,
TFile, TFile,
@ -39,6 +40,15 @@ import {
} from "./macros"; } from "./macros";
import {Obj} from "tern"; 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, { let CollatorCompare = new Intl.Collator(undefined, {
usage: "sort", usage: "sort",
sensitivity: "base", 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.alphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
[CustomSortOrder.trueAlphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(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), [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] return [false, undefined, undefined]
} }
export interface Context { export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: ProcessingContext): FolderItemForSorting {
starredPluginInstance?: Starred_PluginInstance
iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance
}
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: Context): FolderItemForSorting {
let groupIdx: number let groupIdx: number
let determined: boolean = false let determined: boolean = false
let matchedGroup: string | null | undefined let matchedGroup: string | null | undefined
@ -324,10 +329,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
break break
case CustomSortGroupType.HasMetadataField: case CustomSortGroupType.HasMetadataField:
if (group.withMetadataFieldName) { if (group.withMetadataFieldName) {
if (spec._mCache) { if (ctx?._mCache) {
// For folders - scan metadata of 'folder note' // For folders - scan metadata of 'folder note'
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md` 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) const hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
if (hasMetadata) { if (hasMetadata) {
@ -417,10 +422,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
metadataFieldName = DEFAULT_METADATA_FIELD_FOR_SORTING metadataFieldName = DEFAULT_METADATA_FIELD_FOR_SORTING
} }
if (metadataFieldName) { if (metadataFieldName) {
if (spec._mCache) { if (ctx?._mCache) {
// For folders - scan metadata of 'folder note' // For folders - scan metadata of 'folder note'
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md` 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] 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 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 // shallow copy of groups
sortingSpec.groupsShadow = sortingSpec.groups?.map((group) => Object.assign({} as CustomSortGroup, group)) 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) this.file.children)
.map((entry: TFile | TFolder) => { .map((entry: TFile | TFolder) => {
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, { const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, ctx)
starredPluginInstance: starredPluginInstance,
iconFolderPluginInstance: iconFolderPluginInstance
})
return itemForSorting return itemForSorting
}) })
@ -538,8 +537,4 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
} else { } else {
this.children = items; 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 type SortingSpec = string
const checkIfImplicitSpec = (s: SortingSpec) => false
const createMockMatcherRichVersion = (): FolderWildcardMatching<SortingSpec> => { const createMockMatcherRichVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching() const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
let p: string let p: string
p = '/...'; matcher.addWildcardDefinition(p, `00 ${p}`) p = '/...'; matcher.addWildcardDefinition(p, `00 ${p}`)
p = '/*'; matcher.addWildcardDefinition(p, `0 ${p}`) p = '/*'; matcher.addWildcardDefinition(p, `0 ${p}`)
@ -19,25 +21,25 @@ const PRIO2 = 2
const PRIO3 = 3 const PRIO3 = 3
const createMockMatcherSimplestVersion = (): FolderWildcardMatching<SortingSpec> => { const createMockMatcherSimplestVersion = (): 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 return matcher
} }
const createMockMatcherRootOnlyVersion = (): FolderWildcardMatching<SortingSpec> => { const createMockMatcherRootOnlyVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching() const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/...', '/...') matcher.addWildcardDefinition('/...', '/...')
return matcher return matcher
} }
const createMockMatcherRootOnlyDeepVersion = (): FolderWildcardMatching<SortingSpec> => { const createMockMatcherRootOnlyDeepVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching() const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/*', '/*') matcher.addWildcardDefinition('/*', '/*')
return matcher return matcher
} }
const createMockMatcherSimpleVersion = (): FolderWildcardMatching<SortingSpec> => { 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/*')
matcher.addWildcardDefinition('/Reviews/daily/...', '/Reviews/daily/...') matcher.addWildcardDefinition('/Reviews/daily/...', '/Reviews/daily/...')
return matcher return matcher
@ -108,21 +110,21 @@ describe('folderMatch', () => {
expect(match3).toBe('/Reviews/daily/...') expect(match3).toBe('/Reviews/daily/...')
}) })
it('should detect duplicate match children definitions for same path', () => { 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') matcher.addWildcardDefinition('Archive/2020/...', 'First occurrence')
const result = matcher.addWildcardDefinition('/Archive/2020/.../', 'Duplicate') const result = matcher.addWildcardDefinition('/Archive/2020/.../', 'Duplicate')
expect(result).toEqual({errorMsg: "Duplicate wildcard '...' specification for /Archive/2020/.../"}) expect(result).toEqual({errorMsg: "Duplicate wildcard '...' specification for /Archive/2020/.../"})
}) })
it('should detect duplicate match all definitions for same path', () => { 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') matcher.addWildcardDefinition('/Archive/2019/*', 'First occurrence')
const result = matcher.addWildcardDefinition('Archive/2019/*', 'Duplicate') const result = matcher.addWildcardDefinition('Archive/2019/*', 'Duplicate')
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', () => { 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$/, false, undefined, false, `r1`)
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`) matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
matcher.addWildcardDefinition('/Reviews/*', `w1`) matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -134,7 +136,7 @@ describe('folderMatch', () => {
expect(match2).toBe('r2') expect(match2).toBe('r2')
}) })
it('regexp-match by name works (order of regexp doesn\'t matter) reversed case A', () => { 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$/, true, undefined, false, `r2`)
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`) matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
matcher.addWildcardDefinition('/Reviews/*', `w1`) matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -146,7 +148,7 @@ describe('folderMatch', () => {
expect(match2).toBe('r2') expect(match2).toBe('r2')
}) })
it('regexp-match by path works (order of regexp doesn\'t matter) case A', () => { 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$/, false, undefined, false, `r1`)
matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`) matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`)
matcher.addWildcardDefinition('/Reviews/*', `w1`) matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -158,7 +160,7 @@ describe('folderMatch', () => {
expect(match2).toBe('r1') expect(match2).toBe('r1')
}) })
it('regexp-match by path works (order of regexp doesn\'t matter) reversed case A', () => { 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$/, true, undefined, false, `r2`)
matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`) matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`)
matcher.addWildcardDefinition('/Reviews/*', `w1`) matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -170,7 +172,7 @@ describe('folderMatch', () => {
expect(match2).toBe('r1') expect(match2).toBe('r1')
}) })
it('regexp-match by path and name for root level - order of regexp decides - case A', () => { 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$/, false, undefined, false, `r1`)
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`) matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
matcher.addWildcardDefinition('/Reviews/*', `w1`) matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -179,7 +181,7 @@ describe('folderMatch', () => {
expect(match).toBe('r2') expect(match).toBe('r2')
}) })
it('regexp-match by path and name for root level - order of regexp decides - reversed case A', () => { 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$/, true, undefined, false, `r2`)
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`) matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
matcher.addWildcardDefinition('/Reviews/*', `w1`) matcher.addWildcardDefinition('/Reviews/*', `w1`)
@ -188,7 +190,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1') expect(match).toBe('r1')
}) })
it('regexp-match priorities - order of definitions irrelevant - unique priorities - case A', () => { 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, 3, false, `r1p3`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`) matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`) matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
@ -198,7 +200,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3') expect(match).toBe('r1p3')
}) })
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => { 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, undefined, false, `r4pNone`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`) matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`) matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
@ -208,7 +210,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3') expect(match).toBe('r1p3')
}) })
it('regexp-match priorities - order of definitions irrelevant - duplicate priorities - case A', () => { 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, `r1p3a`)
matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3b`) matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3b`)
matcher.addRegexpDefinition(/^daily$/, true, 2, false, `r2p2a`) matcher.addRegexpDefinition(/^daily$/, true, 2, false, `r2p2a`)
@ -221,7 +223,7 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3b') expect(match).toBe('r1p3b')
}) })
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => { 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, undefined, false, `r4pNone`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`) matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`) matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
@ -231,14 +233,14 @@ describe('folderMatch', () => {
expect(match).toBe('r1p3') expect(match).toBe('r1p3')
}) })
it('regexp-match - edge case of matching the root folder - match by path', () => { 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`) matcher.addRegexpDefinition(/^\/$/, false, undefined, false, `r1`)
// Path w/o leading / - this is how Obsidian supplies the path // Path w/o leading / - this is how Obsidian supplies the path
const match: SortingSpec | null = matcher.folderMatch('/', '') const match: SortingSpec | null = matcher.folderMatch('/', '')
expect(match).toBe('r1') expect(match).toBe('r1')
}) })
it('regexp-match - edge case of matching the root folder - match by name not possible', () => { 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 // Tricky regexp which can return zero length matches
matcher.addRegexpDefinition(/.*/, true, undefined, false, `r1`) matcher.addRegexpDefinition(/.*/, true, undefined, false, `r1`)
matcher.addWildcardDefinition('/*', `w1`) matcher.addWildcardDefinition('/*', `w1`)
@ -247,7 +249,7 @@ describe('folderMatch', () => {
expect(match).toBe('w1') expect(match).toBe('w1')
}) })
it('regexp-match - edge case of no match when only regexp rules present', () => { 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 // Tricky regexp which can return zero length matches
matcher.addRegexpDefinition(/abc/, true, undefined, false, `r1`) matcher.addRegexpDefinition(/abc/, true, undefined, false, `r1`)
// Path w/o leading / - this is how Obsidian supplies the path // Path w/o leading / - this is how Obsidian supplies the path

View File

@ -35,6 +35,8 @@ export interface AddingWildcardFailure {
errorMsg: string errorMsg: string
} }
export type CheckIfImplicitSpec<SortingSpec> = (s: SortingSpec) => boolean
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 '/' // 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>> regexps: Array<FolderMatchingRegexp<SortingSpec>>
constructor(private checkIfImplicitSpec: CheckIfImplicitSpec<SortingSpec>) {
}
// cache // cache
determinedWildcardRules: { [key: string]: DeterminedSortingSpec<SortingSpec> } = {} determinedWildcardRules: { [key: string]: DeterminedSortingSpec<SortingSpec> } = {}
@ -68,13 +73,13 @@ export class FolderWildcardMatching<SortingSpec> {
} }
}) })
if (lastComponent === MATCH_CHILDREN_PATH_TOKEN) { if (lastComponent === MATCH_CHILDREN_PATH_TOKEN) {
if (leafNode.matchChildren) { if (leafNode.matchChildren && !this.checkIfImplicitSpec(leafNode.matchChildren)) {
return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`} return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`}
} else { } else {
leafNode.matchChildren = rule leafNode.matchChildren = rule
} }
} else { // Implicitly: MATCH_ALL_PATH_TOKEN } else { // Implicitly: MATCH_ALL_PATH_TOKEN
if (leafNode.matchAll) { if (leafNode.matchAll && !this.checkIfImplicitSpec(leafNode.matchAll)) {
return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`} return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`}
} else { } else {
leafNode.matchAll = rule leafNode.matchAll = rule

View File

@ -1677,6 +1677,11 @@ const txtInputErrorPriorityEmptyPattern: string = `
` `
const txtInputEmptySpec: string = `` const txtInputEmptySpec: string = ``
const txtInputOnlyCommentsSpec: string = `
// Some comment
// Another comment below empty line
`
describe('SortingSpecProcessor error detection and reporting', () => { describe('SortingSpecProcessor error detection and reporting', () => {
let processor: SortingSpecProcessor; 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)}`) `${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')) 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> = ` const inputTxtArr: Array<string> = `
/folders /+ Hello /folders /+ Hello
`.replace(/\t/gi, '').split('\n') `.replace(/\t/gi, '').split('\n')
@ -1932,6 +1937,12 @@ describe('SortingSpecProcessor error detection and reporting', () => {
expect(result).toBeNull() expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(0) 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([ it.each([
'% \\.d+...', '% \\.d+...',
'% ...\\d+', '% ...\\d+',

View File

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