2021-04-10 03:18:32 +02:00
import ScriptUtils from "./ScriptUtils" ;
import { Utils } from "../Utils" ;
2021-04-23 13:56:16 +02:00
import { readFileSync , writeFileSync } from "fs" ;
2021-04-10 03:18:32 +02:00
Utils . runningFromConsole = true
import LayerConfig from "../Customizations/JSON/LayerConfig" ;
import * as licenses from "../assets/generated/license_info.json"
import LayoutConfig from "../Customizations/JSON/LayoutConfig" ;
2021-04-10 03:50:44 +02:00
import { LayerConfigJson } from "../Customizations/JSON/LayerConfigJson" ;
2021-04-23 13:56:16 +02:00
import { Translation } from "../UI/i18n/Translation" ;
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 {
themes : any [ ] , layers : { parsed : any , path : string } [ ]
}
2021-04-10 03:18:32 +02:00
2021-04-23 13:56:16 +02:00
function loadThemesAndLayers ( ) : LayersAndThemes {
2021-04-10 03:18:32 +02:00
const layerFiles = ScriptUtils . readDirRecSync ( "./assets/layers" )
. filter ( path = > path . indexOf ( ".json" ) > 0 )
. filter ( path = > path . indexOf ( "license_info.json" ) < 0 )
. map ( path = > {
try {
const parsed = JSON . parse ( readFileSync ( path , "UTF8" ) ) ;
2021-04-20 10:49:45 +02:00
return { parsed : parsed , path : path }
2021-04-10 03:18:32 +02:00
} catch ( e ) {
2021-04-20 12:20:46 +02:00
console . error ( "Could not parse file " , "./assets/layers/" + path , "due to " , e )
2021-04-10 03:18:32 +02:00
}
} )
2021-04-10 03:50:44 +02:00
const themeFiles : any [ ] = ScriptUtils . readDirRecSync ( "./assets/themes" )
2021-04-10 03:18:32 +02:00
. filter ( path = > path . indexOf ( ".json" ) > 0 )
. filter ( path = > path . indexOf ( "license_info.json" ) < 0 )
. map ( path = > {
return JSON . parse ( readFileSync ( path , "UTF8" ) ) ;
} )
2021-04-18 14:24:30 +02:00
console . log ( "Discovered" , layerFiles . length , "layers and" , themeFiles . length , "themes\n" )
2021-04-23 13:56:16 +02:00
return {
layers : layerFiles ,
themes : themeFiles
}
}
function 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-23 13:56:16 +02:00
function validateLayer ( layerJson : LayerConfigJson , path : string , knownPaths : Set < string > , context? : string ) : string [ ] {
2021-04-10 16:01:14 +02:00
let errorCount = [ ] ;
2021-04-10 03:50:44 +02:00
if ( layerJson [ "overpassTags" ] !== undefined ) {
2021-04-11 03:17:59 +02:00
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)" )
2021-04-10 03:18:32 +02:00
}
2021-04-10 03:50:44 +02:00
try {
const layer = new LayerConfig ( layerJson , "test" , true )
const images = Array . from ( layer . ExtractImages ( ) )
const remoteImages = images . filter ( img = > img . indexOf ( "http" ) == 0 )
for ( const remoteImage of remoteImages ) {
2021-04-23 13:56:16 +02:00
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-10 03:18:32 +02:00
}
2021-04-20 12:20:46 +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-20 10:49:45 +02:00
}
2021-04-20 12:20:46 +02:00
2021-04-10 03:50:44 +02:00
for ( const image of images ) {
2021-04-23 16:52:20 +02:00
if ( image . indexOf ( "{" ) >= 0 ) {
console . warn ( "Ignoring image with { in the path: " , image )
continue
}
2021-04-10 03:50:44 +02:00
if ( ! knownPaths . has ( image ) ) {
2021-04-10 16:01:14 +02:00
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 } ` )
2021-04-10 03:50:44 +02:00
}
}
2021-04-10 14:25:06 +02:00
2021-04-10 03:50:44 +02:00
} catch ( e ) {
2021-04-20 10:49:45 +02:00
console . error ( e )
2021-04-11 01:58:51 +02:00
return [ ` Layer ${ layerJson . id } ` ? ? JSON . stringify ( layerJson ) . substring ( 0 , 50 ) + " is invalid: " + e ]
2021-04-10 03:18:32 +02:00
}
return errorCount
}
2021-04-10 03:50:44 +02:00
2021-04-23 13:56:16 +02:00
function validateTranslationCompletenessOfObject ( object : any , expectedLanguages : string [ ] , context : string ) {
const missingTranlations = [ ]
const translations : { tr : Translation , context : string } [ ] = [ ] ;
const queue : { object : any , context : string } [ ] = [ { object : object , context : context } ]
2021-04-10 03:18:32 +02:00
2021-04-23 13:56:16 +02:00
while ( queue . length > 0 ) {
const item = queue . pop ( ) ;
const o = item . object
for ( const key in o ) {
const v = o [ key ] ;
if ( v === undefined ) {
continue ;
}
if ( v instanceof Translation || v ? . translations !== undefined ) {
translations . push ( { tr : v , context : item.context } ) ;
} else if (
[ "string" , "function" , "boolean" , "number" ] . indexOf ( typeof ( v ) ) < 0 ) {
queue . push ( { object : v , context : item.context + "." + key } )
}
}
}
2021-04-10 03:18:32 +02:00
2021-04-23 13:56:16 +02:00
const missing = { }
const present = { }
for ( const ln of expectedLanguages ) {
missing [ ln ] = 0 ;
present [ ln ] = 0 ;
for ( const translation of translations ) {
if ( translation . tr . translations [ "*" ] !== undefined ) {
continue ;
2021-04-10 03:18:32 +02:00
}
2021-04-23 13:56:16 +02:00
const txt = translation . tr . translations [ ln ] ;
const isMissing = txt === undefined || txt === "" || txt . toLowerCase ( ) . indexOf ( "todo" ) >= 0 ;
if ( isMissing ) {
missingTranlations . push ( ` ${ translation . context } , ${ ln } , ${ translation . tr . txt } ` )
missing [ ln ] ++
2021-04-10 15:53:47 +02:00
} else {
2021-04-23 13:56:16 +02:00
present [ ln ] ++ ;
2021-04-10 15:53:47 +02:00
}
2021-04-10 03:18:32 +02:00
}
}
2021-04-10 03:50:44 +02:00
2021-04-23 13:56:16 +02:00
let message = ` Translation completeness for ${ context } `
let isComplete = true ;
for ( const ln of expectedLanguages ) {
const amiss = missing [ ln ] ;
const ok = present [ ln ] ;
const total = amiss + ok ;
message += ` ${ ln } : ${ ok } / ${ total } `
if ( ok !== total ) {
isComplete = false ;
2021-04-10 03:18:32 +02:00
}
}
2021-04-23 13:56:16 +02:00
return missingTranlations
2021-04-10 03:18:32 +02:00
}
2021-04-10 16:06:01 +02:00
2021-04-23 13:56:16 +02:00
function main ( args : string [ ] ) {
const lt = loadThemesAndLayers ( ) ;
const layerFiles = lt . layers ;
const themeFiles = lt . themes ;
console . log ( " ---------- VALIDATING ---------" )
const licensePaths = [ ]
for ( const i in licenses ) {
licensePaths . push ( licenses [ i ] . path )
}
const knownPaths = new Set < string > ( licensePaths )
let layerErrorCount = [ ]
const knownLayerIds = new Map < string , LayerConfig > ( ) ;
for ( const layerFile of layerFiles ) {
layerErrorCount . push ( . . . validateLayer ( layerFile . parsed , layerFile . path , knownPaths ) )
knownLayerIds . set ( layerFile . parsed . id , new LayerConfig ( layerFile . parsed ) )
}
let themeErrorCount = [ ]
let missingTranslations = [ ]
for ( const themeFile of themeFiles ) {
if ( typeof themeFile . language === "string" ) {
themeErrorCount . push ( "The theme " + themeFile . id + " has a string as language. Please use a list of strings" )
}
for ( const layer of themeFile . layers ) {
if ( typeof layer === "string" ) {
if ( ! knownLayerIds . has ( layer ) ) {
themeErrorCount . push ( ` Unknown layer id: ${ layer } in theme ${ themeFile . id } ` )
} else {
const layerConfig = knownLayerIds . get ( layer ) ;
missingTranslations . push ( . . . validateTranslationCompletenessOfObject ( layerConfig , themeFile . language , "Layer " + layer ) )
}
} else {
if ( layer . builtin !== undefined ) {
if ( ! knownLayerIds . has ( layer . builtin ) ) {
themeErrorCount . push ( "Unknown layer id: " + layer . builtin + "(which uses inheritance)" )
}
} else {
// layer.builtin contains layer overrides - we can skip those
layerErrorCount . push ( . . . validateLayer ( layer , undefined , knownPaths , themeFile . id ) )
}
}
}
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
. filter ( l = > l . builtin === undefined )
missingTranslations . push ( . . . validateTranslationCompletenessOfObject ( themeFile , themeFile . language , "Theme " + themeFile . id ) )
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 )
}
} catch ( e ) {
themeErrorCount . push ( "Could not parse theme " + themeFile [ "id" ] + "due to" , e )
}
2021-04-11 01:58:51 +02:00
}
2021-04-11 03:17:59 +02:00
2021-04-23 13:56:16 +02:00
if ( missingTranslations . length > 0 ) {
2021-04-23 16:52:20 +02:00
console . log ( missingTranslations . length , "missing translations" )
2021-04-23 13:56:16 +02:00
writeFileSync ( "missing_translations.txt" , missingTranslations . join ( "\n" ) )
}
if ( layerErrorCount . length + themeErrorCount . length == 0 ) {
console . log ( "All good!" )
2021-04-23 16:52:20 +02:00
// We load again from disc, as modifications were made above
const lt = loadThemesAndLayers ( ) ;
2021-04-23 13:56:16 +02:00
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 ` )
console . log ( msg )
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-10 15:01:28 +02:00
}
2021-04-10 14:25:06 +02:00
}
2021-04-23 13:56:16 +02:00
main ( process . argv )