Skip to content

Commit 5daa404

Browse files
committed
allow fields/presets to reference the locationSet from other files
1 parent f15c5a2 commit 5daa404

File tree

3 files changed

+99
-24
lines changed

3 files changed

+99
-24
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,11 @@ See the [location-conflation](https://github.com/ideditor/location-conflation) p
368368
}
369369
```
370370

371+
Alternatively, a string can be used to reference the `locationSet` from another field or preset:
372+
```json
373+
"locationSet": "{presets/man_made/crane}"
374+
```
375+
371376
##### `replacement`
372377

373378
The ID of a preset that is preferable to this one. iD's validator will flag features matching this preset and recommend that the user upgrade the tags.
@@ -677,6 +682,11 @@ See the [location-conflation](https://github.com/ideditor/location-conflation) p
677682
}
678683
```
679684

685+
Alternatively, a string can be used to reference the `locationSet` from another field or preset:
686+
```json
687+
"locationSet": "{fields/crane/type}"
688+
```
689+
680690
##### `urlFormat`
681691

682692
For `identifier` fields, the permalink URL of the external record. It must contain a `{value}` placeholder where the tag value will be inserted. For example:

lib/build.js

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,18 @@ function generateTranslations(fields, presets, tstrings, searchableFieldIDs) {
431431
}
432432
});
433433

434-
if (field.locationSet?.include) {
435-
yamlField['#label'] += ` | Local preset for countries ${field.locationSet.include.map(country => `"${country.toUpperCase()}"`).join(', ')}`;
434+
if (field.locationSet) {
435+
const dereferenced = typeof field.locationSet === 'string'
436+
? dereference(field.locationSet, presets, fields)
437+
: field;
438+
const { locationSet } = dereferenced;
439+
440+
if (locationSet.include) {
441+
yamlField['#label'] += ` | Local preset for countries ${locationSet.include.map(country => `"${country.toUpperCase()}"`).join(', ')}`;
442+
}
443+
if (locationSet.exclude) {
444+
yamlField['#label'] += ` | Hidden in some countries: ${locationSet.exclude.map(country => `"${country.toUpperCase()}"`).join(', ')}`;
445+
}
436446
}
437447

438448
if (yamlField.placeholder) {
@@ -470,7 +480,7 @@ function generateTranslations(fields, presets, tstrings, searchableFieldIDs) {
470480
yamlPreset['#name'] += ' | Translate the primary name. Optionally, add equivalent synonyms on newlines in order of preference (press the Return key).';
471481
}
472482

473-
if (preset.locationSet?.include) {
483+
if (typeof preset.locationSet === 'object' && preset.locationSet?.include) {
474484
yamlPreset['#name'] += ` | Local preset for countries ${preset.locationSet.include.map(country => `"${country.toUpperCase()}"`).join(', ')}`;
475485
}
476486

@@ -761,6 +771,40 @@ function validateCategoryPresets(categories, presets) {
761771
});
762772
}
763773

774+
/**
775+
* looks up a string formatted as `{fields/*}` or `{presets/*}`
776+
* @param {string} string
777+
*/
778+
function dereference(string, allPresets, allFields) {
779+
const [type, ...path] = string.slice(1, -1).split('/');
780+
781+
if (type === 'presets') return allPresets[path.join('/')];
782+
if (type === 'fields') return allFields[path.join('/')];
783+
return undefined;
784+
}
785+
786+
/**
787+
* @param {string} id - only for diagnostics
788+
* @param {string} locationSet
789+
*/
790+
function dereferenceLocationSetReference(id, locationSet, presets, fields) {
791+
const match = dereference(locationSet, presets, fields);
792+
793+
if (!match) {
794+
process.stderr.write(`${id}’s locationSet “${locationSet}” refers to a non-existant file.`);
795+
process.stdout.write('\n');
796+
process.exit(1);
797+
}
798+
799+
if (!match.locationSet) {
800+
process.stderr.write(`${id}’s locationSet “${locationSet}” references to a file which has no locationSet.`);
801+
process.stdout.write('\n');
802+
process.exit(1);
803+
}
804+
805+
return match.locationSet;
806+
}
807+
764808
function validatePresetFields(presets, fields) {
765809
const betweenBracketsRegex = /([^{]*?)(?=\})/;
766810
const maxFieldsBeforeError = 10;
@@ -785,6 +829,10 @@ function validatePresetFields(presets, fields) {
785829
}
786830
}
787831

832+
if (typeof preset.locationSet === 'string') {
833+
preset.locationSet = dereferenceLocationSetReference(presetID, preset.locationSet, presets, fields);
834+
}
835+
788836
// the keys for properties that contain arrays of field ids
789837
let fieldKeys = ['fields', 'moreFields'];
790838
for (let fieldsKeyIndex in fieldKeys) {
@@ -849,11 +897,19 @@ function validatePresetFields(presets, fields) {
849897
}
850898

851899
for (let fieldID in fields) {
900+
const field = fields[fieldID];
852901
if (!usedFieldIDs.has(fieldID) &&
853902
fields[fieldID].universal !== true &&
854903
(fields[fieldID].usage || 'preset') === 'preset') {
855904
process.stdout.write('Field "' + fields[fieldID].label + '" (' + fieldID + ') isn\'t used by any presets.\n');
856905
}
906+
907+
if (typeof field.locationSet === 'string') {
908+
// we could overide `field.locationSet` with the response from this function,
909+
// so that data consumers don't have to do any work.
910+
// But we won't do that, to be consistent with existing fields.
911+
dereferenceLocationSetReference(fieldID, field.locationSet, presets, fields);
912+
}
857913
}
858914
}
859915

schemas/field.json

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -311,29 +311,38 @@
311311
}
312312
},
313313
"locationSet": {
314-
"description": "An object specifying the IDs of regions where this field is or isn't valid. See: https://github.com/ideditor/location-conflation",
315-
"type": "object",
316-
"minProperties": 1,
317-
"properties": {
318-
"include": {
319-
"type": "array",
320-
"minItems": 1,
321-
"uniqueItems": true,
322-
"items": {
323-
"type": "string"
324-
}
314+
"oneOf": [
315+
{
316+
"description": "An object specifying the IDs of regions where this field is or isn't valid. See: https://github.com/ideditor/location-conflation",
317+
"type": "object",
318+
"minProperties": 1,
319+
"properties": {
320+
"include": {
321+
"type": "array",
322+
"minItems": 1,
323+
"uniqueItems": true,
324+
"items": {
325+
"type": "string"
326+
}
327+
},
328+
"exclude": {
329+
"type": "array",
330+
"minItems": 1,
331+
"uniqueItems": true,
332+
"items": {
333+
"type": "string"
334+
}
335+
}
336+
},
337+
"additionalProperties": false
325338
},
326-
"exclude": {
327-
"type": "array",
328-
"minItems": 1,
329-
"uniqueItems": true,
330-
"items": {
331-
"type": "string"
332-
}
339+
{
340+
"description": "An string referencing another preset which has a locationSet",
341+
"type": "string",
342+
"pattern": "^\\{.+\\}$"
333343
}
334-
},
335-
"additionalProperties": false
336-
},
344+
]
345+
} ,
337346
"urlFormat": {
338347
"description": "Permalink URL for `identifier` fields. Must contain a {value} placeholder",
339348
"type": "string"

0 commit comments

Comments
 (0)