mapcomplete/scripts/ScriptUtils.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

242 lines
8.9 KiB
TypeScript
Raw Normal View History

import * as fs from "fs"
2023-06-01 14:32:45 +02:00
import { existsSync, lstatSync, readdirSync, readFileSync } from "fs"
import { Utils } from "../src/Utils"
2024-06-16 16:06:26 +02:00
import { https } from "follow-redirects"
import { LayoutConfigJson } from "../src/Models/ThemeConfig/Json/LayoutConfigJson"
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
import xml2js from "xml2js"
2021-04-10 03:18:32 +02:00
export default class ScriptUtils {
public static fixUtils() {
Utils.externalDownloadFunction = ScriptUtils.Download
}
2023-01-09 20:38:05 +01:00
/**
* Returns all files in a directory, recursively reads subdirectories.
* The returned paths include the path given and subdirectories.
*
* @param path
* @param maxDepth
*/
2021-05-20 12:27:33 +02:00
public static readDirRecSync(path, maxDepth = 999): string[] {
2023-01-15 23:28:02 +01:00
const result: string[] = []
if (maxDepth <= 0) {
2021-05-20 12:27:33 +02:00
return []
}
for (const entry of readdirSync(path)) {
const fullEntry = path + "/" + entry
const stats = lstatSync(fullEntry)
if (stats.isDirectory()) {
// Subdirectory
// @ts-ignore
result.push(...ScriptUtils.readDirRecSync(fullEntry, maxDepth - 1))
} else {
result.push(fullEntry)
}
}
2021-04-10 03:18:32 +02:00
return result
}
2021-05-14 17:37:21 +02:00
public static DownloadFileTo(url, targetFilePath: string): Promise<void> {
ScriptUtils.erasableLog("Downloading", url, "to", targetFilePath)
2024-01-24 23:45:20 +01:00
return new Promise<void>((resolve) => {
https.get(url, (res) => {
const filePath = fs.createWriteStream(targetFilePath)
res.pipe(filePath)
filePath.on("finish", () => {
filePath.close()
resolve()
})
})
})
}
2021-09-04 18:59:51 +02:00
public static erasableLog(...text) {
2021-09-04 18:59:51 +02:00
process.stdout.write("\r " + text.join(" ") + " \r")
}
2023-01-09 20:38:05 +01:00
public static sleep(ms: number, text?: string) {
2021-05-14 17:37:21 +02:00
if (ms <= 0) {
process.stdout.write("\r \r")
return
}
return new Promise((resolve) => {
2023-01-09 20:38:05 +01:00
process.stdout.write("\r" + (text ?? "") + " Sleeping for " + ms / 1000 + "s \r")
setTimeout(resolve, 1000)
}).then(() => ScriptUtils.sleep(ms - 1000))
}
public static getLayerPaths(): string[] {
return ScriptUtils.readDirRecSync("./assets/layers")
.filter((path) => path.indexOf(".json") > 0)
.filter((path) => path.indexOf(".proto.json") < 0)
.filter((path) => path.indexOf("license_info.json") < 0)
}
public static getLayerFiles(): { parsed: LayerConfigJson; path: string }[] {
return ScriptUtils.readDirRecSync("./assets/layers")
.filter((path) => path.indexOf(".json") > 0)
2021-09-04 18:59:51 +02:00
.filter((path) => path.indexOf(".proto.json") < 0)
.filter((path) => path.indexOf("license_info.json") < 0)
.map((path) => {
try {
2023-06-01 14:32:45 +02:00
const contents = readFileSync(path, { encoding: "utf8" })
2021-09-04 18:59:51 +02:00
if (contents === "") {
throw "The file " + path + " is empty, did you properly save?"
2021-07-07 15:19:05 +02:00
}
2021-09-04 18:59:51 +02:00
2021-07-07 15:19:05 +02:00
const parsed = JSON.parse(contents)
2023-06-01 14:32:45 +02:00
return { parsed, path }
} catch (e) {
2023-12-12 03:43:34 +01:00
console.error("Could not parse file ", path, "due to ", e)
2022-07-01 02:41:09 +02:00
throw e
}
})
}
public static getThemePaths(): string[] {
return ScriptUtils.readDirRecSync("./assets/themes")
.filter((path) => path.endsWith(".json") && !path.endsWith(".proto.json"))
.filter((path) => path.indexOf("license_info.json") < 0)
}
2023-10-30 13:45:44 +01:00
public static getThemeFiles(): { parsed: LayoutConfigJson; path: string; raw: string }[] {
return this.getThemePaths().map((path) => {
try {
2023-06-01 14:32:45 +02:00
const contents = readFileSync(path, { encoding: "utf8" })
2021-09-04 18:59:51 +02:00
if (contents === "") {
throw "The file " + path + " is empty, did you properly save?"
}
2021-07-07 15:19:05 +02:00
const parsed = JSON.parse(contents)
2023-10-30 13:45:44 +01:00
return { parsed: parsed, path: path, raw: contents }
} catch (e) {
console.error("Could not read file ", path, "due to ", e)
2022-09-08 21:40:48 +02:00
throw e
}
})
}
2021-09-13 01:18:57 +02:00
public static TagInfoHistogram(key: string): Promise<{
data: { count: number; value: string; fraction: number }[]
}> {
const url = `https://taginfo.openstreetmap.org/api/4/key/values?key=${key}&filter=all&lang=en&sortname=count&sortorder=desc&page=1&rp=17&qtype=value`
return ScriptUtils.DownloadJSON(url)
}
public static async ReadSvg(path: string): Promise<SVGElement> {
if (!existsSync(path)) {
throw "File not found: " + path
}
2023-06-01 14:32:45 +02:00
const root = await xml2js.parseStringPromise(readFileSync(path, { encoding: "utf8" }))
return root.svg
}
public static ReadSvgSync(path: string, callback: (svg: any) => void): any {
xml2js.parseString(
2023-06-01 14:32:45 +02:00
readFileSync(path, { encoding: "utf8" }),
{ async: false },
(err, root) => {
if (err) {
throw err
}
callback(root["svg"])
}
)
}
private static async DownloadJSON(url: string, headers?: any): Promise<any> {
const data = await ScriptUtils.Download(url, headers)
return JSON.parse(data["content"])
}
public static async DownloadFetch(
url: string,
headers?: any
): Promise<{ content: string } | { redirect: string }> {
console.log("Fetching", url)
2024-04-13 02:40:21 +02:00
const req = await fetch(url, { headers })
const data = await req.text()
console.log("Fetched", url, data)
return { content: data }
}
public static Download(
url: string,
headers?: any
): Promise<{ content: string } | { redirect: string }>
public static Download(
url: string,
headers?: any,
timeoutSecs?: number
): Promise<{ content: string } | { redirect: string } | "timeout">
public static Download(
url: string,
headers?: any,
timeoutSecs?: number
): Promise<{ content: string } | { redirect: string } | "timeout"> {
2024-08-14 13:53:56 +02:00
if (url.startsWith("./assets")) {
return Promise.resolve({ content: readFileSync("./public/" + url, "utf8") })
2024-08-14 12:42:17 +02:00
}
2024-08-14 13:53:56 +02:00
if (url.startsWith("./")) {
return Promise.resolve({ content: readFileSync(url, "utf8") })
2024-08-11 20:13:04 +02:00
}
const requestPromise = new Promise((resolve, reject) => {
try {
headers = headers ?? {}
2024-06-16 16:06:26 +02:00
if (!headers.Accept) {
2024-04-24 00:58:22 +02:00
headers.accept ??= "application/json"
}
ScriptUtils.erasableLog(" > ScriptUtils.Download(", url, ")")
const urlObj = new URL(url)
const request = https.get(
{
host: urlObj.host,
path: urlObj.pathname + urlObj.search,
2022-09-08 21:40:48 +02:00
port: urlObj.port,
headers: headers,
},
(res) => {
const parts: string[] = []
res.setEncoding("utf8")
res.on("data", function (chunk) {
// @ts-ignore
parts.push(chunk)
})
2022-09-08 21:40:48 +02:00
res.addListener("end", function () {
if (res.statusCode === 301 || res.statusCode === 302) {
console.log("Got a redirect:", res.headers.location)
resolve({ redirect: res.headers.location })
}
if (res.statusCode >= 400) {
console.log(
"Error while fetching ",
url,
"due to",
res.statusMessage
)
reject(res.statusCode)
}
2023-06-01 14:32:45 +02:00
resolve({ content: parts.join("") })
})
2022-09-08 21:40:48 +02:00
}
)
request.on("error", function (e) {
reject(e)
})
} catch (e) {
reject(e)
}
})
const timeoutPromise = new Promise<any>((resolve, reject) => {
2024-06-16 16:06:26 +02:00
setTimeout(() => {
if (timeoutSecs === undefined) {
return // No resolve
}
resolve("timeout")
}, (timeoutSecs ?? 10) * 1000)
})
return Promise.race([requestPromise, timeoutPromise])
}
2021-04-10 03:18:32 +02:00
}