Refactoring: move documentation about tagsFilters into TagUtils
This commit is contained in:
parent
00776c1e6b
commit
48e976e6b7
1 changed files with 209 additions and 6 deletions
|
@ -13,13 +13,201 @@ type Tags = Record<string, string>
|
|||
export type UploadableTag = Tag | SubstitutingTag | And
|
||||
|
||||
export class TagUtils {
|
||||
public static readonly comparators: ReadonlyArray<[string, (a: number, b: number) => boolean]> =
|
||||
[
|
||||
["<=", (a, b) => a <= b],
|
||||
[">=", (a, b) => a >= b],
|
||||
["<", (a, b) => a < b],
|
||||
[">", (a, b) => a > b],
|
||||
]
|
||||
public static modeDocumentation: Record<
|
||||
string,
|
||||
{ name: string; docs: string; uploadable?: boolean; overpassSupport: boolean }
|
||||
> = {
|
||||
"=": {
|
||||
name: "strict equality",
|
||||
uploadable: true,
|
||||
overpassSupport: true,
|
||||
docs:
|
||||
"Strict equality is denoted by `key=value`. This key matches __only if__ the keypair is present exactly as stated.\n" +
|
||||
"\n" +
|
||||
"**Only normal tags (eventually in an `and`) can be used in places where they are uploaded**. Normal tags are used in " +
|
||||
"the `mappings` of a [TagRendering] (unless `hideInAnswer` is specified), they are used in `addExtraTags` of [Freeform] " +
|
||||
"and are used in the `tags`-list of a preset.\n" +
|
||||
"\n" +
|
||||
"If a different kind of tag specification is given, your theme will fail to parse.\n" +
|
||||
"\n" +
|
||||
"### If key is not present\n" +
|
||||
"\n" +
|
||||
"If you want to check if a key is not present, use `key=` (pronounce as *key is empty*). A tag collection will match this\n" +
|
||||
"if `key` is missing or if `key` is a literal empty value.\n" +
|
||||
"\n" +
|
||||
"### Removing a key\n" +
|
||||
"\n" +
|
||||
"If a key should be deleted in the OpenStreetMap-database, specify `key=` as well. This can be used e.g. to remove a\n" +
|
||||
"fixme or value from another mapping if another field is filled out.",
|
||||
},
|
||||
"!=": {
|
||||
name: "strict not equals",
|
||||
overpassSupport: true,
|
||||
docs:
|
||||
"To check if a key does _not_ equal a certain value, use `key!=value`. This is converted behind the scenes\n" +
|
||||
"to `key!~^value$`\n" +
|
||||
"\n" +
|
||||
"If `key` is not present or empty, this will match too.\n" +
|
||||
"\n" +
|
||||
"### If key is present\n" +
|
||||
"\n" +
|
||||
"This implies that, to check if a key is present, `key!=` can be used. This will only match if the key is present and not\n" +
|
||||
"empty.",
|
||||
},
|
||||
"~": {
|
||||
name: "Value matches regex",
|
||||
overpassSupport: true,
|
||||
docs:
|
||||
"A tag can also be tested against a regex with `key~regex`. Note that this regex __must match__ the entire value. If the\n" +
|
||||
"value is allowed to appear anywhere as substring, use `key~.*regex.*`.\n" +
|
||||
"The regex is put within braces as to prevent runaway values.\n" +
|
||||
"\nUse `key~*` to indicate that any value is allowed. This is effectively the check that the attribute is present (defined _and_ not empty)." +
|
||||
"\n" +
|
||||
"Regexes will match the newline character with `.` too - the `s`-flag is enabled by default.",
|
||||
},
|
||||
"~i~": {
|
||||
name: "Value matches case-invariant regex",
|
||||
overpassSupport: true,
|
||||
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value",
|
||||
},
|
||||
"!~": {
|
||||
name: "Value should _not_ match regex",
|
||||
overpassSupport: true,
|
||||
docs:
|
||||
"A tag can also be tested against a regex with `key!~regex`. This filter will match if the value does *not* match the regex. " +
|
||||
"\n If the\n" +
|
||||
"value is allowed to appear anywhere as substring, use `key~.*regex.*`.\n" +
|
||||
"The regex is put within braces as to prevent runaway values.\n",
|
||||
},
|
||||
"!~i~": {
|
||||
name: "Value does *not* match case-invariant regex",
|
||||
overpassSupport: true,
|
||||
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value. This filter returns true if the value does *not* match",
|
||||
},
|
||||
"~~": {
|
||||
name: "Key and value should match given regex",
|
||||
overpassSupport: true,
|
||||
docs: "Both the `key` and `value` part of this specification are interpreted as regexes, both the key and value musth completely match their respective regexes",
|
||||
},
|
||||
":=": {
|
||||
name: "Substitute `{some_key}` should match `key`",
|
||||
overpassSupport: false,
|
||||
docs:
|
||||
"**This is an advanced feature - use with caution**\n" +
|
||||
"\n" +
|
||||
"Some tags are automatically set or calculated - see [CalculatedTags](CalculatedTags.md) for an entire overview. If one\n" +
|
||||
"wants to apply such a value as tag, use a substituting-tag such, for example`survey:date:={_date:now}`. Note that the\n" +
|
||||
"separator between key and value here is `:=`. The text between `{` and `}` is interpreted as a key, and the respective\n" +
|
||||
"value is substituted into the string.\n" +
|
||||
"\n" +
|
||||
"One can also append, e.g. `key:={some_key} fixed text {some_other_key}`.\n" +
|
||||
"\n" +
|
||||
"An assigning tag _cannot_ be used to query OpenStreetMap/Overpass.\n" +
|
||||
"\n" +
|
||||
"If using a key or variable which might not be defined, add a condition in the mapping to hide the option. This is\n" +
|
||||
"because, if `some_other_key` is not defined, one might actually upload the literal text `key={some_other_key}` to OSM -\n" +
|
||||
"which we do not want.\n" +
|
||||
"\n" +
|
||||
"To mitigate this, use:\n" +
|
||||
"\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
' "mappings": [\n' +
|
||||
" {\n" +
|
||||
' "if":"key:={some_other_key}",\n' +
|
||||
' "then": "...",\n' +
|
||||
' "hideInAnswer": "some_other_key="\n' +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}\n" +
|
||||
"```\n" +
|
||||
"\n" +
|
||||
"One can use `key!:=prefix-{other_key}-postfix` as well, to match if `key` is _not_ the same\n" +
|
||||
"as `prefix-{other_key}-postfix` (with `other_key` substituted by the value)",
|
||||
},
|
||||
"!:=": {
|
||||
name: "Substitute `{some_key}` should not match `key`",
|
||||
overpassSupport: false,
|
||||
docs: "See `:=`, except that this filter is inverted",
|
||||
},
|
||||
}
|
||||
private static keyCounts: { keys: any; tags: any } = key_counts
|
||||
private static comparators: [string, (a: number, b: number) => boolean][] = [
|
||||
["<=", (a, b) => a <= b],
|
||||
[">=", (a, b) => a >= b],
|
||||
["<", (a, b) => a < b],
|
||||
[">", (a, b) => a > b],
|
||||
]
|
||||
public static readonly numberAndDateComparisonDocs =
|
||||
"If the value of a tag is a number (e.g. `key=42`), one can use a filter `key<=42`, `key>=35`, `key>40` or `key<50` to\n" +
|
||||
"match this, e.g. in conditions for renderings. These tags cannot be used to generate an answer nor can they be used to\n" +
|
||||
"request data upstream from overpass.\n" +
|
||||
"\n" +
|
||||
"Note that the value coming from OSM will first be stripped by removing all non-numeric characters. For\n" +
|
||||
"example, `length=42 meter` will be interpreted as `length=42` and will thus match `length<=42` and `length>=42`. In\n" +
|
||||
"special circumstances (e.g. `surface_area=42 m2` or `length=100 feet`), this will result in erronous\n" +
|
||||
"values (`surface=422` or if a length in meters is compared to). However, this can be partially alleviated by using '\n" +
|
||||
"Units' to rewrite to a default format.\n" +
|
||||
"\n" +
|
||||
"Dates can be compared with the same expression: `somekey<2022-06-22` will match if `somekey` is a date and is smaller\n" +
|
||||
"then 22nd june '22."
|
||||
|
||||
public static readonly logicalOperator =
|
||||
"\n" +
|
||||
"## Logical operators\n" +
|
||||
"\n" +
|
||||
"One can combine multiple tags by using `and` or `or`, e.g.:\n" +
|
||||
"\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
' "osmTags": {\n' +
|
||||
' "or": [\n' +
|
||||
' "amenity=school",\n' +
|
||||
' "amenity=kindergarten"\n' +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n"
|
||||
|
||||
public static readonly intro =
|
||||
"Tags format\n" +
|
||||
"=============\n" +
|
||||
"\n" +
|
||||
"When creating the `json` file describing your layer or theme, you'll have to add a few tags to describe what you want.\n" +
|
||||
"This document gives an overview of what every expression means and how it behaves in edge cases.\n" +
|
||||
"\n" +
|
||||
"If the schema-files note a type [`TagConfigJson`](https://github.com/pietervdvn/MapComplete/blob/develop/Models/ThemeConfig/Json/TagConfigJson.ts), you can use one of these values.\n" +
|
||||
"\n" +
|
||||
"In some cases, not every type of tags-filter can be used. For example, _rendering_ an option with a regex is\n" +
|
||||
'fine (`"if": "brand~[Bb]randname", "then":" The brand is Brandname"`); but this regex can not be used to write a value\n' +
|
||||
"into the database. The theme loader will however refuse to work with such inconsistencies and notify you of this while\n" +
|
||||
"you are building your theme.\n" +
|
||||
"\n" +
|
||||
"Example\n" +
|
||||
"-------\n" +
|
||||
"\n" +
|
||||
"This example shows the most common options on how to specify tags:\n" +
|
||||
"\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
' "and": [\n' +
|
||||
' "key=value",\n' +
|
||||
" {\n" +
|
||||
' "or": [\n' +
|
||||
' "other_key=value",\n' +
|
||||
' "other_key=some_other_value"\n' +
|
||||
" ]\n" +
|
||||
" },\n" +
|
||||
' "key_which_should_be_missing=",\n' +
|
||||
' "key_which_should_have_a_value~*",\n' +
|
||||
' "key~.*some_regex_a*_b+_[a-z]?",\n' +
|
||||
' "height<1"\n' +
|
||||
" ]\n" +
|
||||
"}\n" +
|
||||
"```\n" +
|
||||
"\n" +
|
||||
"\n"
|
||||
|
||||
static KVtoProperties(tags: Tag[]): Record<string, string> {
|
||||
const properties: Record<string, string> = {}
|
||||
|
@ -688,4 +876,19 @@ export class TagUtils {
|
|||
}
|
||||
return " (" + joined + ") "
|
||||
}
|
||||
|
||||
public static generateDocs(): string {
|
||||
return [
|
||||
TagUtils.intro,
|
||||
...Object.keys(TagUtils.modeDocumentation).map((mode) => {
|
||||
const doc = TagUtils.modeDocumentation[mode]
|
||||
return ["", "## `" + mode + "` " + doc.name, "", doc.docs, "", ""].join("\n")
|
||||
}),
|
||||
"## " +
|
||||
TagUtils.comparators.map((comparator) => "`" + comparator[0] + "`").join(" ") +
|
||||
" Logical comparators",
|
||||
TagUtils.numberAndDateComparisonDocs,
|
||||
TagUtils.logicalOperator,
|
||||
].join("\n")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue