Skip to content

Commit

Permalink
feat(query language): .valueType filter
Browse files Browse the repository at this point in the history
  • Loading branch information
stdword committed Jul 15, 2024
1 parent 4f34cd0 commit 106a38a
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 6 deletions.
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## NEXT

- **Query language for pages**: [`.valueType`](reference__query_language.md#filter-value-type) filter with `.onlyStrings` & `.onlyNumbers` shortcuts to filter properties values by type.



## v4.0 :id=v40

### Set cursor position
Expand Down
49 changes: 44 additions & 5 deletions docs/reference__query_language.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,13 +373,13 @@ Filter by page empty (or non-empty) property value. *Note*: the page may have no


#### `.integerValue`
Filter by page property value. *Note*: this filter treats properties values as an integer numbers.
Filter by page property **integer** value.

?> This filter must be preceded by [`.property`](#filter-property) as it interacts with property's value

!> Property values could be a mix of integers and strings, which introduces some caveats: \
For `=` and `!=` operations, all string values (including empty strings) will be ignored. \
For other comparison operations, all string values will be considered greater than integer values, and there is no way to filter out string values.
!> Property values could be a mix of integers or strings (or sets of references), which introduces comparison caveat: **any string values will be considered greater than integer values**. \
\
If you need to filter out some values types, use [`.valueType`](#filter-value-type) filter.

- `.integerValue(number)` — shortcut for `.integerValue('=', number)`
- `.integerValue(operation, number)`
Expand All @@ -405,10 +405,14 @@ For other comparison operations, all string values will be considered greater th


#### `.value` :id=filter-value
Filter by page property value. *Note*: this filter treats properties values as strings.
Filter by page property **string** value.

?> This filter must be preceded by [`.property`](#filter-property) as it interacts with property's value

!> Property values could be a mix of integers or strings (or sets of references), which introduces comparison caveat: **any string values will be considered greater than integer values**. \
\
If you need to filter out some values types, use [`.valueType`](#filter-value-type) filter.

!> Note for comparison operations: empty string value is less than any other string

- `.value(value)` — shortcut for `.value('=', value)`
Expand Down Expand Up @@ -439,6 +443,41 @@ after 2020: 10 (0 empty)
<!-- tabs:end -->



#### `.valueType` & `.string` & `.number` :id=filter-value-type
Filter by page property value type.

?> This filter must be preceded by [`.property`](#filter-property) as it interacts with property's value

- `.onlyStrings()` — shortcut for `.valueType(['string'])`
- `.onlyNumbers()` — shortcut for `.valueType(['number'])`
- `.valueType(choices)`
- `.valueType(choices, false)` — inversion form
- `choices`: array of strings: `string`, `number` or `set`

<!-- tabs:start -->
#### ***Template***
```javascript
``{
var all = query.pages()
.property('year')
.nonEmpty()
var withNumberYear = all.onlyNumbers().get()
var withStringYear = all.onlyStrings().get()
_}``

year as number count: ``withNumberYear.length``
year as string: ``withStringYear.map(p => p.props.year)``
```

#### ***Rendered***
number years count: 92
string years: 1947-1977 (набор эссе), 1955-1963, 1991 & 2017

<!-- tabs:end -->



#### `.reference` & `.tags` & `.noTags`
Filter by reference in page property value. *Note*: this filter searches references within properties values. And ignores all other text content.

Expand Down
2 changes: 1 addition & 1 deletion src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export class PageContext extends Context {
prefix: parts.slice(0, -1).join('/'),
suffix: parts.at(-1),
pages: parts.slice(0, -1).reduce(
(r, i) => r.concat(parts.slice(0, r.length + 1).join('/')),
(r) => r.concat(parts.slice(0, r.length + 1).join('/')),
[] as Array<string>
),
}) as unknown as PageContext['namespace']
Expand Down
69 changes: 69 additions & 0 deletions src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,58 @@ class ValueFilter extends Filter {
}


class ValueTypeFilter extends Filter {
choices: ('number' | 'string' | 'set')[]

bindingVars = (prop) => [
[`?ptype-${prop}`, `[(type ?p-${prop}) ?ptype-${prop}]`],

[`?type-number`, `[(type 1) ?type-number]`],
[`?type-string`, `[(type "x") ?type-string]`],
[`?type-set`, `[(type #{}) ?type-set]`],
]

constructor(choices: ('number' | 'string' | 'set')[]) {
super('')
this.choices = choices
}
checkArgs(builder: PagesQueryBuilder): string | null {
if (builder.lastState === null)
return 'Preceding property filter is required'
if (this.choices.length === 0)
return 'At least one type is required'

const unknown = this.choices.filter((t) => !['number', 'string', 'set'].includes(t))
if (unknown.length !== 0)
return `Unknown property types: ${unknown.join(', ')}`

return null
}
getPredicate(builder: PagesQueryBuilder): string {
const propertyName = builder.lastState

const lines: string[] = []
for (const choice of this.choices) {
let predicate: string

if (choice === 'string')
predicate = `[(= ?ptype-${propertyName} ?type-string)]`
else if (choice === 'number')
predicate = `[(= ?ptype-${propertyName} ?type-number)]`
else if (choice === 'set')
predicate = `[(= ?ptype-${propertyName} ?type-set)]`
else return ''

lines.push(predicate)
}

if (lines.length === 1)
return lines[0]
return `(or ${lines.join('\n')} )`
}
}


class ReferenceFilter extends Filter {
values: string[]
operation: string
Expand Down Expand Up @@ -352,12 +404,14 @@ export class PagesQueryBuilder {
noProperty(name: string) {
return this._filter(new PropertyFilter(name), false)
}

empty() {
return this._filter(new EmptyFilter())
}
nonEmpty() {
return this._filter(new EmptyFilter(), false)
}

integerValue(operation: string, value: string = '') {
if (value === '') {
value = operation
Expand All @@ -374,6 +428,20 @@ export class PagesQueryBuilder {
value = value.toString()
return this._filter(new ValueFilter(value, operation), nonInverted)
}

valueType(choices: ('number' | 'string' | 'set')[] = [], nonInverted: boolean = true) {
choices = choices.map(x => x.toString() as ('number' | 'string' | 'set'))
if (choices.length === 0)
return this
return this._filter(new ValueTypeFilter(choices), nonInverted)
}
onlyStrings(nonInverted: boolean = true) {
return this.valueType(['string'], nonInverted)
}
onlyNumbers(nonInverted: boolean = true) {
return this.valueType(['number'], nonInverted)
}

reference(operation: string, value: string | string[] = '', nonInverted: boolean = true) {
if (value === '') {
value = operation
Expand All @@ -389,6 +457,7 @@ export class PagesQueryBuilder {
value = value.toString()
return this._filter(new ReferenceCountFilter(value, operation), nonInverted)
}

tags(names: string | string[] = '', only: boolean = false) {
const cloned = this._filter(new PropertyFilter('tags'))
return cloned._filter(new ReferenceFilter(names, only ? 'includes only' : 'includes'))
Expand Down

0 comments on commit 106a38a

Please sign in to comment.