79 foldername variable support (#81)

* #79 - parent-folder-name variable support

- introductions of simplistic macros / templating support
- initially only one macro supported: {:%parent-folder-name%:}
  - macro expanded only for plain-text matching rules, ignored for regexp-based rules
  - for children of the root folder the macro is ignored
- unit tests for the new macros.ts
- unit tests for the testable part of updated custom-sort.ts
This commit is contained in:
SebastianMC 2023-06-30 19:05:29 +02:00 committed by GitHub
parent 5900452e8a
commit 037ada5a88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 177 additions and 2 deletions

View File

@ -72,6 +72,8 @@ export interface CustomSortSpec {
defaultOrder?: CustomSortOrder defaultOrder?: CustomSortOrder
byMetadataField?: string // for 'by-metadata:' if the defaultOrder is by metadata alphabetical or reverse byMetadataField?: string // for 'by-metadata:' if the defaultOrder is by metadata alphabetical or reverse
groups: Array<CustomSortGroup> groups: Array<CustomSortGroup>
groupsShadow?: Array<CustomSortGroup> // 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)
outsidersGroupIdx?: number outsidersGroupIdx?: number
outsidersFilesGroupIdx?: number outsidersFilesGroupIdx?: number
outsidersFoldersGroupIdx?: number outsidersFoldersGroupIdx?: number

View File

@ -704,6 +704,33 @@ describe('determineSortingGroup', () => {
path: 'Some parent folder/References.md' path: 'Some parent folder/References.md'
}); });
}) })
it('should consume shadow group instead of group, if shadow is present', () => {
// given
const file: TFile = mockTFile('gs-123', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
const sortSpec: CustomSortSpec = {
targetFoldersPaths: ['/'],
groups: [{
type: CustomSortGroupType.ExactName,
exactText: 'g-123'
}],
groupsShadow: [{
type: CustomSortGroupType.ExactName,
exactText: 'gs-123'
}]
}
// when
const result = determineSortingGroup(file, sortSpec)
// then
expect(result).toEqual({
groupIdx: 0, // This indicates match!
isFolder: false,
sortString: "gs-123.md",
ctime: MOCK_TIMESTAMP + 222,
mtime: MOCK_TIMESTAMP + 333,
path: 'Some parent folder/gs-123.md'
});
})
}) })
describe('CustomSortGroupType.byMetadataFieldAlphabetical', () => { describe('CustomSortGroupType.byMetadataFieldAlphabetical', () => {
it('should ignore the file item if it has no direct metadata', () => { it('should ignore the file item if it has no direct metadata', () => {

View File

@ -31,7 +31,13 @@ import {
NormalizerFn, NormalizerFn,
RegExpSpec RegExpSpec
} from "./custom-sort-types"; } from "./custom-sort-types";
import {isDefined} from "../utils/utils"; import {
isDefined
} from "../utils/utils";
import {
expandMacros
} from "./macros";
import {Obj} from "tern";
let CollatorCompare = new Intl.Collator(undefined, { let CollatorCompare = new Intl.Collator(undefined, {
usage: "sort", usage: "sort",
@ -184,7 +190,7 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
for (let idx = 0; idx < numOfGroupsToCheck; idx++) { for (let idx = 0; idx < numOfGroupsToCheck; idx++) {
matchedGroup = null matchedGroup = null
groupIdx = spec.priorityOrder ? spec.priorityOrder[idx] : idx groupIdx = spec.priorityOrder ? spec.priorityOrder[idx] : idx
const group: CustomSortGroup = spec.groups[groupIdx]; const group: CustomSortGroup = spec.groupsShadow ? spec.groupsShadow[groupIdx] : spec.groups[groupIdx];
if (group.foldersOnly && aFile) continue; if (group.foldersOnly && aFile) continue;
if (group.filesOnly && aFolder) continue; if (group.filesOnly && aFolder) continue;
const nameForMatching: string = group.matchFilenameWithExt ? entry.name : basename; const nameForMatching: string = group.matchFilenameWithExt ? entry.name : basename;
@ -428,6 +434,13 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app) const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app)
const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance | undefined = getIconFolderPlugin(sortingSpec?.plugin?.app) const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance | undefined = getIconFolderPlugin(sortingSpec?.plugin?.app)
// shallow copy of groups
sortingSpec.groupsShadow = sortingSpec.groups?.map((group) => Object.assign({} as CustomSortGroup, group))
// expand folder-specific macros
const parentFolderName: string|undefined = this.file.name
expandMacros(sortingSpec, parentFolderName)
const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ? const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
this.file.children.filter((entry: TFile | TFolder) => { this.file.children.filter((entry: TFile | TFolder) => {
return !sortingSpec.itemsToHide!.has(entry.name) return !sortingSpec.itemsToHide!.has(entry.name)

View File

@ -0,0 +1,99 @@
import {expandMacros, expandMacrosInString} from "./macros";
import * as MacrosModule from './macros'
import {CustomSortGroup, CustomSortSpec} from "./custom-sort-types";
describe('expandMacrosInString', () => {
it.each([
['', ''],
['123', '123'],
[' 123 ', ' 123 '],
[' Abc{:%parent-folder-name%:}Def ', ' Abc{:%parent-folder-name%:}Def '],
['{:%parent-folder-name%:}Def ', '{:%parent-folder-name%:}Def '],
[' Abc{:%parent-folder-name%:}', ' Abc{:%parent-folder-name%:}'],
[' {:%parent-folder-name%:} xyz {:%parent-folder-name%:}', ' {:%parent-folder-name%:} xyz {:%parent-folder-name%:}'],
[' {:%unknown%:} ',' {:%unknown%:} ']
])('%s should transform to %s when no parent folder', (source: string, expanded: string) => {
const result1 = expandMacrosInString(source)
const result2 = expandMacrosInString(source, '')
expect(result1).toBe(expanded)
expect(result2).toBe(expanded)
})
it.each([
['', ''],
['123', '123'],
[' 123 ', ' 123 '],
[' Abc{:%parent-folder-name%:}Def ', ' AbcSubFolder 5Def '],
['{:%parent-folder-name%:}Def ', 'SubFolder 5Def '],
[' Abc{:%parent-folder-name%:}', ' AbcSubFolder 5'],
[' {:%parent-folder-name%:} xyz {:%parent-folder-name%:}', ' SubFolder 5 xyz {:%parent-folder-name%:}'],
[' {:%unknown%:} ',' {:%unknown%:} ']
])('%s should transform to %s when parent folder specified', (source: string, expanded: string) => {
const PARENT = 'SubFolder 5'
const result = expandMacrosInString(source, PARENT)
expect(result).toBe(expanded)
})
})
function mockGroup(gprefix: string, group: string, prefix: string, full: string, suffix: string): CustomSortGroup {
const g: Partial<CustomSortGroup> = {
exactText: gprefix + group + full,
exactPrefix: gprefix + group + prefix,
exactSuffix: gprefix + group + suffix
}
return g as CustomSortGroup
}
describe('expandMacros', () => {
it('should invoke expand in all relevant text fields on all groups', () => {
const sortSpec: Partial<CustomSortSpec> = {
groups: [
mockGroup('g-', '1-', 'abc', 'def', 'ghi'),
mockGroup('g-', '2-', 'abc', 'def', 'ghi'),
],
groupsShadow: [
mockGroup('gs-', '1-', 'abc', 'def', 'ghi'),
mockGroup('gs-', '2-', 'abc', 'def', 'ghi'),
]
}
const sp = jest.spyOn(MacrosModule, 'expandMacrosInString')
const ParentFolder = 'Parent folder name'
expandMacros(sortSpec as CustomSortSpec, ParentFolder)
expect(sp).toBeCalledTimes(6)
expect(sp).toHaveBeenNthCalledWith(1, 'gs-1-def', ParentFolder)
expect(sp).toHaveBeenNthCalledWith(2, 'gs-1-abc', ParentFolder)
expect(sp).toHaveBeenNthCalledWith(3, 'gs-1-ghi', ParentFolder)
expect(sp).toHaveBeenNthCalledWith(4, 'gs-2-def', ParentFolder)
expect(sp).toHaveBeenNthCalledWith(5, 'gs-2-abc', ParentFolder)
expect(sp).toHaveBeenNthCalledWith(6, 'gs-2-ghi', ParentFolder)
})
it('should expand correctly in all relevant text fields on all groups, based on shadow groups', () => {
const sortSpec: Partial<CustomSortSpec> = {
groups: [
mockGroup('g-', '1-', 'abc{:%parent-folder-name%:}', 'de{:%parent-folder-name%:}f', '{:%parent-folder-name%:}ghi'),
mockGroup('g-', '2-', '{:%parent-folder-name%:}abc', 'd{:%parent-folder-name%:}ef', 'ghi{:%parent-folder-name%:}'),
],
groupsShadow: [
mockGroup('gs-', '1-', 'abc{:%parent-folder-name%:}', 'de{:%parent-folder-name%:}f', '{:%parent-folder-name%:}ghi'),
mockGroup('gs-', '2-', '{:%parent-folder-name%:}abc', 'd{:%parent-folder-name%:}ef', 'ghi{:%parent-folder-name%:}'),
]
}
const originalSortSpec: Partial<CustomSortSpec> = {
groups: [...sortSpec.groups!],
groupsShadow: [...sortSpec.groupsShadow!]
}
const ParentFolder = 'Parent folder name'
expandMacros(sortSpec as CustomSortSpec, ParentFolder)
expect(sortSpec.groups![0].exactText).toBe(originalSortSpec.groups![0].exactText)
expect(sortSpec.groups![0].exactPrefix).toBe(originalSortSpec.groups![0].exactPrefix)
expect(sortSpec.groups![0].exactSuffix).toBe(originalSortSpec.groups![0].exactSuffix)
expect(sortSpec.groups![1].exactText).toBe(originalSortSpec.groups![1].exactText)
expect(sortSpec.groups![1].exactPrefix).toBe(originalSortSpec.groups![1].exactPrefix)
expect(sortSpec.groups![1].exactSuffix).toBe(originalSortSpec.groups![1].exactSuffix)
expect(sortSpec.groupsShadow![0].exactText).toBe('gs-1-deParent folder namef')
expect(sortSpec.groupsShadow![0].exactPrefix).toBe('gs-1-abcParent folder name')
expect(sortSpec.groupsShadow![0].exactSuffix).toBe('gs-1-Parent folder nameghi')
expect(sortSpec.groupsShadow![1].exactText).toBe('gs-2-dParent folder nameef')
expect(sortSpec.groupsShadow![1].exactPrefix).toBe('gs-2-Parent folder nameabc')
expect(sortSpec.groupsShadow![1].exactSuffix).toBe('gs-2-ghiParent folder name')
})
})

34
src/custom-sort/macros.ts Normal file
View File

@ -0,0 +1,34 @@
import {
CustomSortSpec
} from "./custom-sort-types";
const MACRO_PREFIX: string = '{:'
const MACRO_SUFFIX: string = ':}'
const PARENT_FOLDER_NAME_PLACEHOLDER: string = '%parent-folder-name%'
const PARENT_FOLDER_NAME_MACRO: string = MACRO_PREFIX + PARENT_FOLDER_NAME_PLACEHOLDER + MACRO_SUFFIX
export const expandMacrosInString = function(source: string|undefined, parentFolderName?: string|undefined): string|undefined {
if (source && parentFolderName) {
return source.replace(PARENT_FOLDER_NAME_MACRO, parentFolderName)
} else {
return source
}
}
export const expandMacros = function(sortingSpec: CustomSortSpec, parentFolderName: string|undefined) {
sortingSpec.groupsShadow?.forEach((shadowGroup) => {
if (parentFolderName) { // root has no parent folder, ignore relevant macros for the root
if (shadowGroup.exactText) {
shadowGroup.exactText = expandMacrosInString(shadowGroup.exactText, parentFolderName)
}
if (shadowGroup.exactPrefix) {
shadowGroup.exactPrefix = expandMacrosInString(shadowGroup.exactPrefix, parentFolderName)
}
if (shadowGroup.exactSuffix) {
shadowGroup.exactSuffix = expandMacrosInString(shadowGroup.exactSuffix, parentFolderName)
}
}
})
}