2021-04-10 03:18:32 +02:00
import ScriptUtils from "./ScriptUtils" ;
2021-06-23 02:41:30 +02:00
import { writeFileSync } from "fs" ;
2021-04-10 03:18:32 +02:00
import * as licenses from "../assets/generated/license_info.json"
2021-08-07 23:11:34 +02:00
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson" ;
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" ;
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" ;
import LayerConfig from "../Models/ThemeConfig/LayerConfig" ;
2021-10-01 04:49:19 +02:00
import { Translation } from "../UI/i18n/Translation" ;
import { Utils } from "../Utils" ;
2021-06-23 02:41:30 +02:00
2021-04-10 03:18:32 +02:00
// This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
// It spits out an overview of those to be used to load them
2021-04-23 13:56:16 +02:00
interface LayersAndThemes {
2021-05-19 20:47:41 +02:00
themes : any [ ] ,
layers : { parsed : any , path : string } [ ]
2021-04-23 13:56:16 +02:00
}
2021-04-10 03:18:32 +02:00
2021-05-19 23:31:00 +02:00
class LayerOverviewUtils {
2021-05-19 20:47:41 +02:00
loadThemesAndLayers ( ) : LayersAndThemes {
2021-05-19 23:31:00 +02:00
const layerFiles = ScriptUtils . getLayerFiles ( ) ;
2021-05-19 20:47:41 +02:00
2021-05-19 23:40:55 +02:00
const themeFiles : LayoutConfigJson [ ] = ScriptUtils . getThemeFiles ( ) . map ( x = > x . parsed ) ;
2021-05-19 20:47:41 +02:00
console . log ( "Discovered" , layerFiles . length , "layers and" , themeFiles . length , "themes\n" )
2021-09-04 18:59:51 +02:00
if ( layerFiles . length + themeFiles . length === 0 ) {
throw "Panic: no themes and layers loaded!"
}
2021-05-19 20:47:41 +02:00
return {
layers : layerFiles ,
themes : themeFiles
2021-04-10 03:50:44 +02:00
}
2021-05-19 20:47:41 +02:00
}
2021-04-10 14:25:06 +02:00
2021-05-19 20:47:41 +02:00
writeFiles ( lt : LayersAndThemes ) {
writeFileSync ( "./assets/generated/known_layers_and_themes.json" , JSON . stringify ( {
"layers" : lt . layers . map ( l = > l . parsed ) ,
"themes" : lt . themes
} ) )
2021-04-10 03:18:32 +02:00
}
2021-04-10 03:50:44 +02:00
2021-05-19 20:47:41 +02:00
validateLayer ( layerJson : LayerConfigJson , path : string , knownPaths : Set < string > , context? : string ) : string [ ] {
let errorCount = [ ] ;
if ( layerJson [ "overpassTags" ] !== undefined ) {
errorCount . push ( "Layer " + layerJson . id + "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)" )
}
try {
2021-09-13 01:21:47 +02:00
const layer = new LayerConfig ( layerJson , "test" , true )
2021-05-19 20:47:41 +02:00
const images = Array . from ( layer . ExtractImages ( ) )
const remoteImages = images . filter ( img = > img . indexOf ( "http" ) == 0 )
for ( const remoteImage of remoteImages ) {
errorCount . push ( "Found a remote image: " + remoteImage + " in layer " + layer . id + ", please download it. You can use the fixTheme script to automate this" )
2021-04-23 13:56:16 +02:00
}
2021-05-19 20:47:41 +02:00
const expected : string = ` assets/layers/ ${ layer . id } / ${ layer . id } .json `
if ( path != undefined && path . indexOf ( expected ) < 0 ) {
errorCount . push ( "Layer is in an incorrect place. The path is " + path + ", but expected " + expected )
2021-04-23 13:56:16 +02:00
}
2021-09-04 18:59:51 +02:00
if ( layerJson [ "hideUnderlayingFeaturesMinPercentage" ] !== undefined ) {
errorCount . push ( "Layer " + layer . id + " contains an old 'hideUnderlayingFeaturesMinPercentage'" )
}
2021-05-19 20:47:41 +02:00
for ( const image of images ) {
if ( image . indexOf ( "{" ) >= 0 ) {
console . warn ( "Ignoring image with { in the path: " , image )
continue
}
if ( ! knownPaths . has ( image ) ) {
const ctx = context === undefined ? "" : ` in a layer defined in the theme ${ context } `
errorCount . push ( ` Image with path ${ image } not found or not attributed; it is used in ${ layer . id } ${ ctx } ` )
}
}
} catch ( e ) {
console . error ( e )
return [ ` Layer ${ layerJson . id } ` ? ? JSON . stringify ( layerJson ) . substring ( 0 , 50 ) + " is invalid: " + e ]
2021-04-23 13:56:16 +02:00
}
2021-05-19 20:47:41 +02:00
return errorCount
2021-04-23 13:56:16 +02:00
}
2021-04-10 03:18:32 +02:00
2021-05-19 20:47:41 +02:00
main ( args : string [ ] ) {
2021-04-10 16:06:01 +02:00
2021-07-26 10:13:50 +02:00
const layerFiles = ScriptUtils . getLayerFiles ( ) ;
const themeFiles = ScriptUtils . getThemeFiles ( ) ;
2021-04-23 13:56:16 +02:00
2021-07-12 11:44:55 +02:00
2021-05-19 20:47:41 +02:00
console . log ( " ---------- VALIDATING ---------" )
const licensePaths = [ ]
for ( const i in licenses ) {
licensePaths . push ( licenses [ i ] . path )
}
const knownPaths = new Set < string > ( licensePaths )
2021-04-23 13:56:16 +02:00
2021-05-19 20:47:41 +02:00
let layerErrorCount = [ ]
const knownLayerIds = new Map < string , LayerConfig > ( ) ;
for ( const layerFile of layerFiles ) {
2021-04-23 13:56:16 +02:00
2021-07-12 11:44:55 +02:00
if ( knownLayerIds . has ( layerFile . parsed . id ) ) {
throw "Duplicate identifier: " + layerFile . parsed . id + " in file " + layerFile . path
}
2021-05-19 20:47:41 +02:00
layerErrorCount . push ( . . . this . validateLayer ( layerFile . parsed , layerFile . path , knownPaths ) )
2021-09-13 01:21:47 +02:00
knownLayerIds . set ( layerFile . parsed . id , new LayerConfig ( layerFile . parsed ) )
2021-04-23 13:56:16 +02:00
}
2021-05-19 20:47:41 +02:00
let themeErrorCount = [ ]
2021-07-26 10:13:50 +02:00
for ( const themeInfo of themeFiles ) {
const themeFile = themeInfo . parsed
const themePath = themeInfo . path
2021-05-19 20:47:41 +02:00
if ( typeof themeFile . language === "string" ) {
themeErrorCount . push ( "The theme " + themeFile . id + " has a string as language. Please use a list of strings" )
}
2021-09-13 01:21:47 +02:00
if ( themeFile [ "units" ] !== undefined ) {
themeErrorCount . push ( "The theme " + themeFile . id + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " )
}
2021-05-19 20:47:41 +02:00
for ( const layer of themeFile . layers ) {
if ( typeof layer === "string" ) {
if ( ! knownLayerIds . has ( layer ) ) {
themeErrorCount . push ( ` Unknown layer id: ${ layer } in theme ${ themeFile . id } ` )
2021-04-23 13:56:16 +02:00
}
2021-07-27 19:59:41 +02:00
} else if ( layer [ "builtin" ] !== undefined ) {
let names = layer [ "builtin" ] ;
2021-07-12 11:44:55 +02:00
if ( typeof names === "string" ) {
names = [ names ]
}
names . forEach ( name = > {
if ( ! knownLayerIds . has ( name ) ) {
themeErrorCount . push ( "Unknown layer id: " + name + "(which uses inheritance)" )
2021-05-19 20:47:41 +02:00
}
2021-07-12 11:44:55 +02:00
return
} )
} else {
2021-07-27 19:59:41 +02:00
layerErrorCount . push ( . . . this . validateLayer ( < LayerConfigJson > layer , undefined , knownPaths , themeFile . id ) )
if ( knownLayerIds . has ( layer [ "id" ] ) ) {
throw ` The theme ${ themeFile . id } defines a layer with id ${ layer [ "id" ] } , which is the same as an already existing layer `
2021-05-19 20:47:41 +02:00
}
2021-04-23 13:56:16 +02:00
}
}
2021-10-01 04:49:19 +02:00
const referencedLayers = Utils . NoNull ( themeFile . layers . map ( layer = > {
if ( typeof layer === "string" ) {
return layer
}
if ( layer [ "builtin" ] !== undefined ) {
return layer [ "builtin" ]
}
return undefined
} ) )
2021-04-23 13:56:16 +02:00
2021-05-19 20:47:41 +02:00
themeFile . layers = themeFile . layers
. filter ( l = > typeof l != "string" ) // We remove all the builtin layer references as they don't work with ts-node for some weird reason
2021-07-26 10:13:50 +02:00
. filter ( l = > l [ "builtin" ] === undefined )
2021-04-23 13:56:16 +02:00
2021-05-19 20:47:41 +02:00
try {
const theme = new LayoutConfig ( themeFile , true , "test" )
if ( theme . id !== theme . id . toLowerCase ( ) ) {
themeErrorCount . push ( "Theme ids should be in lowercase, but it is " + theme . id )
}
2021-07-26 10:13:50 +02:00
let filename = themePath . substring ( themePath . lastIndexOf ( "/" ) + 1 , themePath . length - 5 )
2021-09-04 18:59:51 +02:00
if ( theme . id !== filename ) {
themeErrorCount . push ( "Theme ids should be the same as the name.json, but we got id: " + theme . id + " and filename " + filename + " (" + themePath + ")" )
2021-07-26 10:13:50 +02:00
}
2021-10-01 04:49:19 +02:00
const neededLanguages = themeFile [ "mustHaveLanguage" ]
if ( neededLanguages !== undefined ) {
console . log ( "Checking language requerements for " , theme . id , "as it must have" , neededLanguages . join ( ", " ) )
const allTranslations = [ ] . concat ( Translation . ExtractAllTranslationsFrom ( theme , theme . id ) , . . . referencedLayers . map ( layerId = > Translation . ExtractAllTranslationsFrom ( knownLayerIds . get ( layerId ) , theme . id + "->" + layerId ) ) )
for ( const neededLanguage of neededLanguages ) {
allTranslations
. filter ( t = > t . tr . translations [ neededLanguage ] === undefined && t . tr . translations [ "*" ] === undefined )
. forEach ( missing = > {
themeErrorCount . push ( "The theme " + theme . id + " should be translation-complete for " + neededLanguage + ", but it lacks a translation for " + missing . context )
} )
}
}
2021-09-04 18:59:51 +02:00
2021-05-19 20:47:41 +02:00
} catch ( e ) {
themeErrorCount . push ( "Could not parse theme " + themeFile [ "id" ] + "due to" , e )
2021-04-23 13:56:16 +02:00
}
}
2021-05-19 20:47:41 +02:00
if ( layerErrorCount . length + themeErrorCount . length == 0 ) {
console . log ( "All good!" )
// We load again from disc, as modifications were made above
const lt = this . loadThemesAndLayers ( ) ;
this . writeFiles ( lt ) ;
} else {
const errors = layerErrorCount . concat ( themeErrorCount ) . join ( "\n" )
console . log ( errors )
const msg = ( ` Found ${ layerErrorCount . length } errors in the layers; ${ themeErrorCount . length } errors in the themes ` )
2021-06-24 02:35:41 +02:00
console . log ( "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" )
2021-06-22 00:29:07 +02:00
2021-05-19 20:47:41 +02:00
console . log ( msg )
2021-06-24 02:35:41 +02:00
console . log ( "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" )
2021-05-19 20:47:41 +02:00
if ( process . argv . indexOf ( "--report" ) >= 0 ) {
console . log ( "Writing report!" )
writeFileSync ( "layer_report.txt" , errors )
}
if ( process . argv . indexOf ( "--no-fail" ) < 0 ) {
throw msg ;
}
2021-04-23 13:56:16 +02:00
}
2021-04-10 15:01:28 +02:00
}
2021-04-10 14:25:06 +02:00
}
2021-04-23 13:56:16 +02:00
2021-06-24 02:35:41 +02:00
new LayerOverviewUtils ( ) . main ( process . argv )