diff --git a/spec/filter/util.spec.ts b/spec/filter/util.spec.ts new file mode 100644 index 0000000..1d4a450 --- /dev/null +++ b/spec/filter/util.spec.ts @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025, J2 Innovations. All Rights Reserved + */ + +import { HDict } from '../../src/core/dict/HDict' +import { HRef } from '../../src/core/HRef' +import { makeRelativeHaystackFilter } from '../../src/filter/util' +import { HMarker } from '../../src/core/HMarker' +import { HStr } from '../../src/core/HStr' + +describe('haystack', () => { + describe('makeRelativeHaystackFilter()', () => { + it('returns a haystack filter with dis', () => { + expect( + makeRelativeHaystackFilter( + new HDict({ + id: HRef.make('id'), + dis: 'an equip', + equip: HMarker.make(), + }) + ) + ).toEqual('equip and dis == "an equip"') + }) + + it('returns a haystack filter without a display name with the option disabled', () => { + expect( + makeRelativeHaystackFilter( + new HDict({ + id: HRef.make('id'), + dis: 'an equip', + equip: HMarker.make(), + }), + { + useDisplayName: false, + } + ) + ).toEqual('equip') + }) + + it('returns a haystack filter with a nav name', () => { + expect( + makeRelativeHaystackFilter( + new HDict({ + id: HRef.make('id'), + navName: 'an equip', + equip: HMarker.make(), + }) + ) + ).toEqual('equip and navName == "an equip"') + }) + + it('returns a haystack filter with a point kind', () => { + expect( + makeRelativeHaystackFilter( + new HDict({ + id: HRef.make('id'), + navName: 'a point', + point: HMarker.make(), + kind: HStr.make('Number'), + }) + ) + ).toEqual('point and navName == "a point" and kind == "Number"') + }) + + it('returns a haystack filter without the point kind and the option disabled', () => { + expect( + makeRelativeHaystackFilter( + new HDict({ + id: HRef.make('id'), + navName: 'a point', + point: HMarker.make(), + kind: HStr.make('Number'), + }), + { + useKind: false, + } + ) + ).toEqual('point and navName == "a point"') + }) + + it('returns a haystack filter with an absolute id as a fallback', () => { + expect( + makeRelativeHaystackFilter( + new HDict({ + id: HRef.make('id'), + }) + ) + ).toEqual('id == @id') + }) + }) // makeRelativeHaystackFilter() +}) diff --git a/src/filter/util.ts b/src/filter/util.ts new file mode 100644 index 0000000..1076d8f --- /dev/null +++ b/src/filter/util.ts @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025, J2 Innovations. All Rights Reserved + */ + +import { HDict } from '../core/dict/HDict' +import { HMarker } from '../core/HMarker' +import { HRef } from '../core/HRef' +import { HStr } from '../core/HStr' +import { valueIsKind } from '../core/HVal' +import { Kind } from '../core/Kind' +import { HFilterBuilder } from '../filter/HFilterBuilder' + +/** + * Relativization options. + */ +export interface RelativizeOptions { + /** + * True (or undefined) if the display name should be used in the relativization. + */ + useDisplayName?: boolean + + /** + * True (or undefined) if a point's kind should be used in the relativization. + */ + useKind?: boolean +} + +/** + * Makes a relative haystack filter from a record. + * + * @param record The record. + * @returns A haystack filter. + */ +export function makeRelativeHaystackFilter( + record: HDict, + options?: RelativizeOptions +): string { + const useDisplayName = options?.useDisplayName ?? true + const useKind = options?.useKind ?? true + + const builder = new HFilterBuilder() + + // Build the marker tags. + for (const { name, value } of record) { + if (valueIsKind(value, Kind.Marker)) { + if (!builder.isEmpty()) { + builder.and() + } + + builder.has(name) + } + } + + if (useDisplayName) { + // Build the display name match if one is available. + if (!addDisplayNameToFilter(record, builder, 'dis')) { + if (!addDisplayNameToFilter(record, builder, 'name')) { + if (!addDisplayNameToFilter(record, builder, 'tag')) { + addDisplayNameToFilter(record, builder, 'navName') + } + } + } + } + + if (useKind && record.has('point') && record.has('kind')) { + addPointKindToFilter(record, builder) + } + + if (builder.isEmpty() && record.has('id')) { + builder.equals('id', record.get('id') as HRef) + } + + return builder.build() +} + +function addPointKindToFilter(record: HDict, builder: HFilterBuilder): void { + const kind = record.get('kind')?.value + if (kind) { + if (!builder.isEmpty()) { + builder.and() + } + builder.equals('kind', kind) + } +} + +function addDisplayNameToFilter( + record: HDict, + builder: HFilterBuilder, + tagName: string +): boolean { + // Build the display name match. + const name = record.get(tagName)?.value + if (name) { + if (!builder.isEmpty()) { + builder.and() + } + + builder.equals(tagName, name) + return true + } + return false +} diff --git a/src/index.ts b/src/index.ts index 70e1188..e8d5946 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ export * from './filter/tokens' export * from './filter/TokenType' export * from './filter/TokenValue' export * from './filter/HFilterBuilder' +export * from './filter/util' // Util export * from './util/LocalizedError'