From eeb581120d1dc146ee33ff4997588de4f9b9ebc0 Mon Sep 17 00:00:00 2001 From: SebastianMC <23032356+SebastianMC@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:23:23 +0100 Subject: [PATCH] Major refactoring of sorting structures. Refactored the code. Unit tests not touched, broken. --- src/custom-sort/custom-sort-types.ts | 36 +++++------ src/custom-sort/custom-sort-utils.ts | 8 +-- src/custom-sort/custom-sort.ts | 63 ++++++++++--------- src/custom-sort/sorting-spec-processor.ts | 77 ++++++++--------------- 4 files changed, 78 insertions(+), 106 deletions(-) diff --git a/src/custom-sort/custom-sort-types.ts b/src/custom-sort/custom-sort-types.ts index e2afd46..6eac641 100644 --- a/src/custom-sort/custom-sort-types.ts +++ b/src/custom-sort/custom-sort-types.ts @@ -50,15 +50,6 @@ export enum CustomSortOrder { default = alphabeticalWithFilesPreferred } -export interface RecognizedOrderValue { - order: CustomSortOrder - applyToMetadataField?: string - metadataValueExtractor?: MDataExtractor - secondaryOrder?: CustomSortOrder - secondaryApplyToMetadataField?: string - secondaryMetadataValueExtractor?: MDataExtractor -} - export type NormalizerFn = (s: string) => string | null export const IdentityNormalizerFn: NormalizerFn = (s: string) => s @@ -67,6 +58,17 @@ export interface RegExpSpec { normalizerFn?: NormalizerFn } +export interface CustomSort { + order: CustomSortOrder // mandatory + byMetadata?: string + metadataValueExtractor?: MDataExtractor +} + +export interface RecognizedSorting { + primary?: CustomSort + secondary?: CustomSort +} + export interface CustomSortGroup { type: CustomSortGroupType exactText?: string @@ -74,12 +76,8 @@ export interface CustomSortGroup { regexPrefix?: RegExpSpec exactSuffix?: string regexSuffix?: RegExpSpec - order?: CustomSortOrder - byMetadataField?: string // for 'by-metadata:' sorting if the order is by metadata alphabetical or reverse - metadataFieldValueExtractor?: MDataExtractor // and its sorting value extractor - secondaryOrder?: CustomSortOrder - byMetadataFieldSecondary?: string // for 'by-metadata:' sorting if the order is by metadata alphabetical or reverse - metadataFieldSecondaryValueExtractor?: MDataExtractor + sorting?: CustomSort + secondarySorting?: CustomSort filesOnly?: boolean matchFilenameWithExt?: boolean foldersOnly?: boolean @@ -92,12 +90,8 @@ export interface CustomSortGroup { export interface CustomSortSpec { // plays only informative role about the original parsed 'target-folder:' values targetFoldersPaths: Array // For root use '/' - defaultOrder?: CustomSortOrder - defaultSecondaryOrder?: CustomSortOrder - byMetadataField?: string // for 'by-metadata:' if the defaultOrder is by metadata - metadataFieldValueExtractor?: MDataExtractor // and its sorting value extractor - byMetadataFieldSecondary?: string - metadataFieldSecondaryValueExtractor?: MDataExtractor + defaultSorting?: CustomSort + defaultSecondarySorting?: CustomSort groups: Array groupsShadow?: Array // A shallow copy of groups, used at applying sorting for items in a folder. // Stores folder-specific values (e.g. macros expanded with folder-specific values) diff --git a/src/custom-sort/custom-sort-utils.ts b/src/custom-sort/custom-sort-utils.ts index e3f0ed0..d686ff4 100644 --- a/src/custom-sort/custom-sort-utils.ts +++ b/src/custom-sort/custom-sort-utils.ts @@ -63,12 +63,12 @@ export const collectSortingAndGroupingTypes = (sortSpec?: CustomSortSpec|null): } } if (!sortSpec) return has - doCheck(has, sortSpec.defaultOrder) - doCheck(has, sortSpec.defaultSecondaryOrder) + doCheck(has, sortSpec.defaultSorting?.order) + doCheck(has, sortSpec.defaultSecondarySorting?.order) if (sortSpec.groups) { for (let group of sortSpec.groups) { - doCheck(has, group.order, group.type) - doCheck(has, group.secondaryOrder) + doCheck(has, group.sorting?.order, group.type) + doCheck(has, group.secondarySorting?.order) } } return has diff --git a/src/custom-sort/custom-sort.ts b/src/custom-sort/custom-sort.ts index 6da64a2..6abdde2 100644 --- a/src/custom-sort/custom-sort.ts +++ b/src/custom-sort/custom-sort.ts @@ -17,6 +17,7 @@ import { ObsidianIconFolder_PluginInstance } from '../utils/ObsidianIconFolderPluginSignature' import { + CustomSort, CustomSortGroup, CustomSortGroupType, CustomSortOrder, @@ -294,18 +295,18 @@ export const StandardPlainObsidianComparator = (order: string): PlainSorterFn => } } -export const getSorterFnFor = (sorting: CustomSortOrder, currentUIselectedSorting?: string, sortLevelId?: SortingLevelId): SorterFn => { - if (sorting === CustomSortOrder.standardObsidian) { - sorting = StandardObsidianToCustomSort[currentUIselectedSorting ?? 'alphabetical'] ?? CustomSortOrder.alphabetical - return StandardObsidianComparator(sorting) +export const getSorterFnFor = (order: CustomSortOrder, currentUIselectedSorting?: string, sortLevelId?: SortingLevelId): SorterFn => { + if (order === CustomSortOrder.standardObsidian) { + order = StandardObsidianToCustomSort[currentUIselectedSorting ?? 'alphabetical'] ?? CustomSortOrder.alphabetical + return StandardObsidianComparator(order) } else { // Some sorters have to know at which sorting level they are used switch(sortLevelId) { - case SortingLevelId.forSecondary: return SortersForSecondary[sorting] ?? Sorters[sorting] - case SortingLevelId.forDerivedPrimary: return SortersForDerivedPrimary[sorting] ?? Sorters[sorting] - case SortingLevelId.forDerivedSecondary: return SortersForDerivedSecondary[sorting] ?? Sorters[sorting] + case SortingLevelId.forSecondary: return SortersForSecondary[order] ?? Sorters[order] + case SortingLevelId.forDerivedPrimary: return SortersForDerivedPrimary[order] ?? Sorters[order] + case SortingLevelId.forDerivedSecondary: return SortersForDerivedSecondary[order] ?? Sorters[order] case SortingLevelId.forPrimary: - default: return Sorters[sorting] + default: return Sorters[order] } } } @@ -315,13 +316,13 @@ export const getComparator = (sortSpec: CustomSortSpec, currentUIselectedSorting if (itA.groupIdx != undefined && itB.groupIdx != undefined) { if (itA.groupIdx === itB.groupIdx) { const group: CustomSortGroup | undefined = sortSpec.groups[itA.groupIdx] - const primary: number = group?.order ? getSorterFnFor(group.order, currentUIselectedSorting, SortingLevelId.forPrimary)(itA, itB) : EQUAL_OR_UNCOMPARABLE + const primary: number = group?.sorting ? getSorterFnFor(group.sorting.order, currentUIselectedSorting, SortingLevelId.forPrimary)(itA, itB) : EQUAL_OR_UNCOMPARABLE if (primary !== EQUAL_OR_UNCOMPARABLE) return primary - const secondary: number = group?.secondaryOrder ? getSorterFnFor(group.secondaryOrder, currentUIselectedSorting, SortingLevelId.forSecondary)(itA, itB) : EQUAL_OR_UNCOMPARABLE + const secondary: number = group?.secondarySorting ? getSorterFnFor(group.secondarySorting.order, currentUIselectedSorting, SortingLevelId.forSecondary)(itA, itB) : EQUAL_OR_UNCOMPARABLE if (secondary !== EQUAL_OR_UNCOMPARABLE) return secondary - const folderLevel: number = sortSpec.defaultOrder ? getSorterFnFor(sortSpec.defaultOrder, currentUIselectedSorting, SortingLevelId.forDerivedPrimary)(itA, itB) : EQUAL_OR_UNCOMPARABLE + const folderLevel: number = sortSpec.defaultSorting ? getSorterFnFor(sortSpec.defaultSorting.order, currentUIselectedSorting, SortingLevelId.forDerivedPrimary)(itA, itB) : EQUAL_OR_UNCOMPARABLE if (folderLevel !== EQUAL_OR_UNCOMPARABLE) return folderLevel - const folderLevelSecondary: number = sortSpec.defaultSecondaryOrder ? getSorterFnFor(sortSpec.defaultSecondaryOrder, currentUIselectedSorting, SortingLevelId.forDerivedSecondary)(itA, itB) : EQUAL_OR_UNCOMPARABLE + const folderLevelSecondary: number = sortSpec.defaultSecondarySorting ? getSorterFnFor(sortSpec.defaultSecondarySorting.order, currentUIselectedSorting, SortingLevelId.forDerivedSecondary)(itA, itB) : EQUAL_OR_UNCOMPARABLE if (folderLevelSecondary !== EQUAL_OR_UNCOMPARABLE) return folderLevelSecondary const defaultForUnspecified: number = getSorterFnFor(CustomSortOrder.default, undefined, SortingLevelId.forDefaultWhenUnspecified)(itA, itB) return defaultForUnspecified @@ -567,10 +568,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus if (determined && determinedGroupIdx !== undefined) { // <-- defensive code, maybe too defensive const group: CustomSortGroup = spec.groups[determinedGroupIdx]; - const isPrimaryOrderByMetadata: boolean = isByMetadata(group?.order) - const isSecondaryOrderByMetadata: boolean = isByMetadata(group?.secondaryOrder) - const isDerivedPrimaryByMetadata: boolean = isByMetadata(spec.defaultOrder) - const isDerivedSecondaryByMetadata: boolean = isByMetadata(spec.defaultSecondaryOrder) + const isPrimaryOrderByMetadata: boolean = isByMetadata(group?.sorting?.order) + const isSecondaryOrderByMetadata: boolean = isByMetadata(group?.secondarySorting?.order) + const isDerivedPrimaryByMetadata: boolean = isByMetadata(spec.defaultSorting?.order) + const isDerivedSecondaryByMetadata: boolean = isByMetadata(spec.defaultSecondarySorting?.order) if (isPrimaryOrderByMetadata || isSecondaryOrderByMetadata || isDerivedPrimaryByMetadata || isDerivedSecondaryByMetadata) { if (ctx?._mCache) { // For folders - scan metadata of 'folder note' @@ -586,26 +587,26 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus } if (isPrimaryOrderByMetadata) metadataValueToSortBy = mdataValueFromFMCaches ( - group?.byMetadataField || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, - group?.metadataFieldValueExtractor, + group.sorting!.byMetadata || group.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, + group.sorting!.metadataValueExtractor, frontMatterCache, prioFrontMatterCache) if (isSecondaryOrderByMetadata) metadataValueSecondaryToSortBy = mdataValueFromFMCaches ( - group?.byMetadataFieldSecondary || group?.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, - group?.metadataFieldSecondaryValueExtractor, + group.secondarySorting!.byMetadata || group.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING, + group.secondarySorting!.metadataValueExtractor, frontMatterCache, prioFrontMatterCache) if (isDerivedPrimaryByMetadata) metadataValueDerivedPrimaryToSortBy = mdataValueFromFMCaches ( - spec.byMetadataField || DEFAULT_METADATA_FIELD_FOR_SORTING, - spec.metadataFieldValueExtractor, + spec.defaultSorting!.byMetadata || DEFAULT_METADATA_FIELD_FOR_SORTING, + spec.defaultSorting!.metadataValueExtractor, frontMatterCache, prioFrontMatterCache) if (isDerivedSecondaryByMetadata) metadataValueDerivedSecondaryToSortBy = mdataValueFromFMCaches ( - spec.byMetadataFieldSecondary || DEFAULT_METADATA_FIELD_FOR_SORTING, - spec.metadataFieldSecondaryValueExtractor, + spec.defaultSecondarySorting!.byMetadata || DEFAULT_METADATA_FIELD_FOR_SORTING, + spec.defaultSecondarySorting!.metadataValueExtractor, frontMatterCache, prioFrontMatterCache) } @@ -707,12 +708,12 @@ export const determineDatesForFolder = (folder: TFolder, recursive?: boolean): [ } export const determineFolderDatesIfNeeded = (folderItems: Array, sortingSpec: CustomSortSpec) => { - const foldersDatesNeeded = sortOrderNeedsFolderDates(sortingSpec.defaultOrder, sortingSpec.defaultSecondaryOrder) - const foldersDeepDatesNeeded = sortOrderNeedsFolderDeepDates(sortingSpec.defaultOrder, sortingSpec.defaultSecondaryOrder) + const foldersDatesNeeded = sortOrderNeedsFolderDates(sortingSpec.defaultSorting?.order, sortingSpec.defaultSecondarySorting?.order) + const foldersDeepDatesNeeded = sortOrderNeedsFolderDeepDates(sortingSpec.defaultSorting?.order, sortingSpec.defaultSecondarySorting?.order) const groupOrders = sortingSpec.groups?.map((group) => ({ - foldersDatesNeeded: sortOrderNeedsFolderDates(group.order, group.secondaryOrder), - foldersDeepDatesNeeded: sortOrderNeedsFolderDeepDates(group.order, group.secondaryOrder) + foldersDatesNeeded: sortOrderNeedsFolderDates(group.sorting?.order, group.secondarySorting?.order), + foldersDeepDatesNeeded: sortOrderNeedsFolderDeepDates(group.sorting?.order, group.secondarySorting?.order) })) folderItems.forEach((item) => { @@ -732,15 +733,15 @@ export const determineFolderDatesIfNeeded = (folderItems: Array, sortingSpec: CustomSortSpec, plugin: BookmarksPluginInterface) => { if (!plugin) return - const folderDefaultSortRequiresBookmarksOrder: boolean = !!(sortingSpec.defaultOrder && sortOrderNeedsBookmarksOrder(sortingSpec.defaultOrder, sortingSpec.defaultSecondaryOrder)) + const folderDefaultSortRequiresBookmarksOrder: boolean = !!(sortingSpec.defaultSorting && sortOrderNeedsBookmarksOrder(sortingSpec.defaultSorting.order, sortingSpec.defaultSecondarySorting?.order)) folderItems.forEach((item) => { let groupSortRequiresBookmarksOrder: boolean = false if (!folderDefaultSortRequiresBookmarksOrder) { const groupIdx: number | undefined = item.groupIdx if (groupIdx !== undefined) { - const groupOrder: CustomSortOrder | undefined = sortingSpec.groups[groupIdx].order - const groupSecondaryOrder: CustomSortOrder | undefined = sortingSpec.groups[groupIdx].secondaryOrder + const groupOrder: CustomSortOrder | undefined = sortingSpec.groups[groupIdx].sorting?.order + const groupSecondaryOrder: CustomSortOrder | undefined = sortingSpec.groups[groupIdx].secondarySorting?.order groupSortRequiresBookmarksOrder = sortOrderNeedsBookmarksOrder(groupOrder, groupSecondaryOrder) } } diff --git a/src/custom-sort/sorting-spec-processor.ts b/src/custom-sort/sorting-spec-processor.ts index 591fea5..935a63e 100644 --- a/src/custom-sort/sorting-spec-processor.ts +++ b/src/custom-sort/sorting-spec-processor.ts @@ -1,4 +1,5 @@ import { + CustomSort, CustomSortGroup, CustomSortGroupType, CustomSortOrder, @@ -6,7 +7,7 @@ import { DEFAULT_METADATA_FIELD_FOR_SORTING, IdentityNormalizerFn, NormalizerFn, - RecognizedOrderValue, + RecognizedSorting, RegExpSpec } from "./custom-sort-types"; import {isDefined, last} from "../utils/utils"; @@ -113,12 +114,6 @@ interface CustomSortOrderAscDescPair { desc: CustomSortOrder } -interface CustomSortOrderSpec { - order: CustomSortOrder - byMetadataField?: string - metadataFieldExtractor?: MDataExtractor -} - const MAX_SORT_LEVEL: number = 1 // remember about .toLowerCase() before comparison! @@ -1081,34 +1076,28 @@ export class SortingSpecProcessor { if (!this.ctx.currentSpec) { this.ctx.currentSpec = this.putNewSpecForNewTargetFolder() } - if (this.ctx.currentSpec.defaultOrder) { + if (this.ctx.currentSpec.defaultSorting) { const folderPathsForProblemMsg: string = this.ctx.currentSpec.targetFoldersPaths.join(' :: '); this.problem(ProblemCode.DuplicateOrderAttr, `Duplicate order specification for folder(s) ${folderPathsForProblemMsg}`) return false; } - this.ctx.currentSpec.defaultOrder = (attr.value as RecognizedOrderValue).order - this.ctx.currentSpec.byMetadataField = (attr.value as RecognizedOrderValue).applyToMetadataField - this.ctx.currentSpec.metadataFieldValueExtractor = (attr.value as RecognizedOrderValue).metadataValueExtractor - this.ctx.currentSpec.defaultSecondaryOrder = (attr.value as RecognizedOrderValue).secondaryOrder - this.ctx.currentSpec.byMetadataFieldSecondary = (attr.value as RecognizedOrderValue).secondaryApplyToMetadataField - this.ctx.currentSpec.metadataFieldSecondaryValueExtractor = (attr.value as RecognizedOrderValue).secondaryMetadataValueExtractor + const rs: RecognizedSorting = attr.value // Syntax sugar + this.ctx.currentSpec.defaultSorting = rs.primary + this.ctx.currentSpec.defaultSecondarySorting = rs.secondary return true; } else if (attr.nesting > 0) { // For now only distinguishing nested (indented) and not-nested (not-indented), the depth doesn't matter if (!this.ctx.currentSpec || !this.ctx.currentSpecGroup) { this.problem(ProblemCode.DanglingOrderAttr, `Nested (indented) attribute requires prior sorting group definition`) return false; } - if (this.ctx.currentSpecGroup.order) { + if (this.ctx.currentSpecGroup.sorting) { const folderPathsForProblemMsg: string = this.ctx.currentSpec.targetFoldersPaths.join(' :: '); this.problem(ProblemCode.DuplicateOrderAttr, `Duplicate order specification for a sorting rule of folder ${folderPathsForProblemMsg}`) return false; } - this.ctx.currentSpecGroup.order = (attr.value as RecognizedOrderValue).order - this.ctx.currentSpecGroup.byMetadataField = (attr.value as RecognizedOrderValue).applyToMetadataField - this.ctx.currentSpecGroup.metadataFieldValueExtractor = (attr.value as RecognizedOrderValue).metadataValueExtractor - this.ctx.currentSpecGroup.secondaryOrder = (attr.value as RecognizedOrderValue).secondaryOrder - this.ctx.currentSpecGroup.byMetadataFieldSecondary = (attr.value as RecognizedOrderValue).secondaryApplyToMetadataField - this.ctx.currentSpecGroup.metadataFieldSecondaryValueExtractor = (attr.value as RecognizedOrderValue).secondaryMetadataValueExtractor + const rs: RecognizedSorting = attr.value // Syntax sugar + this.ctx.currentSpecGroup.sorting = rs.primary + this.ctx.currentSpecGroup.secondarySorting = rs.secondary return true; } } @@ -1397,7 +1386,7 @@ export class SortingSpecProcessor { currentCombinedGroupIdx = i } else { // Ensure that the preceding group doesn't contain sorting order - if (spec.groups[i - 1].order) { + if (spec.groups[i - 1].sorting) { this.problem(ProblemCode.OnlyLastCombinedGroupCanSpecifyOrder, 'Predecessor group of combined group cannot contain order specification. Put it at the last of group in combined groups') return false } @@ -1412,34 +1401,26 @@ export class SortingSpecProcessor { // Populate sorting order within combined groups if (anyCombinedGroupPresent) { - let orderForCombinedGroup: CustomSortOrder | undefined - let byMetadataFieldForCombinedGroup: string | undefined - let secondaryOrderForCombinedGroup: CustomSortOrder | undefined - let secondaryByMetadataFieldForCombinedGroup: string | undefined + let sortingForCombinedGroup: CustomSort|undefined + let secondarySortingForCombinedGroup: CustomSort|undefined let idxOfCurrentCombinedGroup: number | undefined = undefined for (let i = spec.groups.length - 1; i >= 0; i--) { const group: CustomSortGroup = spec.groups[i] if (group.combineWithIdx !== undefined) { if (group.combineWithIdx === idxOfCurrentCombinedGroup) { // a subsequent (2nd, 3rd, ...) group of combined (counting from the end) - group.order = orderForCombinedGroup - group.byMetadataField = byMetadataFieldForCombinedGroup - group.secondaryOrder = secondaryOrderForCombinedGroup - group.byMetadataFieldSecondary = secondaryByMetadataFieldForCombinedGroup + group.sorting = sortingForCombinedGroup + group.secondarySorting = secondarySortingForCombinedGroup } else { // the first group of combined (counting from the end) idxOfCurrentCombinedGroup = group.combineWithIdx - orderForCombinedGroup = group.order // could be undefined - byMetadataFieldForCombinedGroup = group.byMetadataField // could be undefined - secondaryOrderForCombinedGroup = group.secondaryOrder // could be undefined - secondaryByMetadataFieldForCombinedGroup = group.byMetadataFieldSecondary // could be undefined + sortingForCombinedGroup = group.sorting + secondarySortingForCombinedGroup = group.secondarySorting } } else { // for sanity idxOfCurrentCombinedGroup = undefined - orderForCombinedGroup = undefined - byMetadataFieldForCombinedGroup = undefined - secondaryOrderForCombinedGroup = undefined - secondaryByMetadataFieldForCombinedGroup = undefined + sortingForCombinedGroup = undefined + secondarySortingForCombinedGroup = undefined } } } @@ -1481,13 +1462,13 @@ export class SortingSpecProcessor { } } - private internalValidateOrderAttrValue = (sortOrderSpecText: string, prefixLexeme: string): Array|AttrError|null => { + private internalValidateOrderAttrValue = (sortOrderSpecText: string, prefixLexeme: string): Array|AttrError|null => { if (sortOrderSpecText.indexOf(CommentPrefix) >= 0) { sortOrderSpecText = sortOrderSpecText.substring(0, sortOrderSpecText.indexOf(CommentPrefix)) } const sortLevels: Array = `${prefixLexeme||''} ${sortOrderSpecText}`.trim().split(OrderLevelsSeparator) - let sortOrderSpec: Array = [] + let sortOrderSpec: Array = [] // Max two levels are supported, excess levels specs are ignored for (let level: number = 0; level <= MAX_SORT_LEVEL && level < sortLevels.length; level++) { @@ -1583,22 +1564,18 @@ export class SortingSpecProcessor { } sortOrderSpec[level] = { order: order!, - byMetadataField: metadataName, - metadataFieldExtractor: metadataExtractor + byMetadata: metadataName, + metadataValueExtractor: metadataExtractor } } return sortOrderSpec } - private validateOrderAttrValue: AttrValueValidatorFn = (v: string, attr: Attribute, attrLexeme: string): RecognizedOrderValue|AttrError|null => { - const recognized: Array|AttrError|null = this.internalValidateOrderAttrValue(v, attrLexeme) + private validateOrderAttrValue: AttrValueValidatorFn = (v: string, attr: Attribute, attrLexeme: string): RecognizedSorting|AttrError|null => { + const recognized: Array|AttrError|null = this.internalValidateOrderAttrValue(v, attrLexeme) return recognized ? (recognized instanceof AttrError ? recognized : { - order: recognized[0].order, - applyToMetadataField: recognized[0].byMetadataField, - metadataValueExtractor: recognized[0].metadataFieldExtractor, - secondaryOrder: recognized[1]?.order, - secondaryApplyToMetadataField: recognized[1]?.byMetadataField, - secondaryMetadataValueExtractor: recognized[1]?.metadataFieldExtractor + primary: recognized[0], + secondary: recognized[1] }) : null; }