gtfs-to-html 2.9.2 → 2.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/index.js +30 -19
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +92 -64
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +91 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/views/default/css/timetable_styles.css +155 -1
- package/views/default/formatting_functions.pug +13 -12
- package/views/default/js/timetable-alerts.js +180 -0
- package/views/default/js/timetable-map.js +10 -5
- package/views/default/timetablepage.pug +12 -4
- package/views/default/timetablepage_full.pug +7 -6
- package/views/default/css/timetable_pdf_styles.css +0 -69
package/dist/bin/gtfs-to-html.js
CHANGED
|
@@ -11,10 +11,17 @@ import { hideBin } from "yargs/helpers";
|
|
|
11
11
|
import PrettyError from "pretty-error";
|
|
12
12
|
|
|
13
13
|
// src/lib/file-utils.ts
|
|
14
|
-
import
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
15
|
import { createWriteStream } from "node:fs";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
access,
|
|
19
|
+
cp,
|
|
20
|
+
copyFile,
|
|
21
|
+
mkdir,
|
|
22
|
+
readdir,
|
|
23
|
+
readFile
|
|
24
|
+
} from "node:fs/promises";
|
|
18
25
|
import _ from "lodash-es";
|
|
19
26
|
import archiver from "archiver";
|
|
20
27
|
import beautify from "js-beautify";
|
|
@@ -323,7 +330,7 @@ function formatTripNameForCSV(trip, timetable) {
|
|
|
323
330
|
}
|
|
324
331
|
|
|
325
332
|
// package.json
|
|
326
|
-
var version = "2.9.
|
|
333
|
+
var version = "2.9.3";
|
|
327
334
|
|
|
328
335
|
// src/lib/utils.ts
|
|
329
336
|
var isTimepoint = (stoptime) => {
|
|
@@ -1302,7 +1309,10 @@ function setDefaultConfig(initialConfig) {
|
|
|
1302
1309
|
config.menuType = "none";
|
|
1303
1310
|
}
|
|
1304
1311
|
config.hasGtfsRealtime = config.agencies.some(
|
|
1305
|
-
(agency) => agency.realtimeTripUpdates?.url || agency.realtimeVehiclePositions?.url
|
|
1312
|
+
(agency) => agency.realtimeTripUpdates?.url || agency.realtimeVehiclePositions?.url || agency.realtimeAlerts?.url
|
|
1313
|
+
);
|
|
1314
|
+
config.hasGtfsRealtimeAlerts = config.agencies.some(
|
|
1315
|
+
(agency) => agency.realtimeAlerts?.url
|
|
1306
1316
|
);
|
|
1307
1317
|
return config;
|
|
1308
1318
|
}
|
|
@@ -1789,7 +1799,7 @@ async function getConfig(argv2) {
|
|
|
1789
1799
|
let data;
|
|
1790
1800
|
let config;
|
|
1791
1801
|
try {
|
|
1792
|
-
data = await readFile(
|
|
1802
|
+
data = await readFile(resolve(untildify(argv2.configPath)), "utf8");
|
|
1793
1803
|
} catch (error) {
|
|
1794
1804
|
throw new Error(
|
|
1795
1805
|
`Cannot find configuration file at \`${argv2.configPath}\`. Use config-sample.json as a starting point, pass --configPath option`
|
|
@@ -1810,61 +1820,77 @@ async function getConfig(argv2) {
|
|
|
1810
1820
|
}
|
|
1811
1821
|
return config;
|
|
1812
1822
|
}
|
|
1813
|
-
function
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
fullTemplateFileName += "_full";
|
|
1823
|
+
function getPathToViewsFolder(config) {
|
|
1824
|
+
if (config.templatePath) {
|
|
1825
|
+
return untildify(config.templatePath);
|
|
1817
1826
|
}
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1827
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1828
|
+
let viewsFolderPath;
|
|
1829
|
+
if (__dirname.endsWith("/dist/bin") || __dirname.endsWith("/dist/app")) {
|
|
1830
|
+
viewsFolderPath = resolve(__dirname, "../../views/default");
|
|
1831
|
+
} else if (__dirname.endsWith("/dist")) {
|
|
1832
|
+
viewsFolderPath = resolve(__dirname, "../views/default");
|
|
1833
|
+
} else {
|
|
1834
|
+
viewsFolderPath = resolve(__dirname, "views/default");
|
|
1835
|
+
}
|
|
1836
|
+
return viewsFolderPath;
|
|
1837
|
+
}
|
|
1838
|
+
function getPathToTemplateFile(templateFileName, config) {
|
|
1839
|
+
const fullTemplateFileName = config.noHead !== true ? `${templateFileName}_full.pug` : `${templateFileName}.pug`;
|
|
1840
|
+
return join(getPathToViewsFolder(config), fullTemplateFileName);
|
|
1820
1841
|
}
|
|
1821
|
-
async function prepDirectory(
|
|
1822
|
-
await rm(exportPath, { recursive: true, force: true });
|
|
1842
|
+
async function prepDirectory(outputPath) {
|
|
1823
1843
|
try {
|
|
1824
|
-
await
|
|
1844
|
+
await access(outputPath);
|
|
1825
1845
|
} catch (error) {
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
)
|
|
1846
|
+
try {
|
|
1847
|
+
await mkdir(outputPath, { recursive: true });
|
|
1848
|
+
} catch (error2) {
|
|
1849
|
+
if (error2?.code === "ENOENT") {
|
|
1850
|
+
throw new Error(
|
|
1851
|
+
`Unable to write to ${outputPath}. Try running this command from a writable directory.`
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
throw error2;
|
|
1830
1855
|
}
|
|
1831
|
-
|
|
1856
|
+
}
|
|
1857
|
+
const files = await readdir(outputPath);
|
|
1858
|
+
if (files.length > 0) {
|
|
1859
|
+
throw new Error(
|
|
1860
|
+
`Output directory ${outputPath} is not empty. Please specify an empty directory.`
|
|
1861
|
+
);
|
|
1832
1862
|
}
|
|
1833
1863
|
}
|
|
1834
|
-
async function copyStaticAssets(config,
|
|
1835
|
-
const
|
|
1864
|
+
async function copyStaticAssets(config, outputPath) {
|
|
1865
|
+
const viewsFolderPath = getPathToViewsFolder(config);
|
|
1836
1866
|
const foldersToCopy = ["css", "js", "img"];
|
|
1837
1867
|
for (const folder of foldersToCopy) {
|
|
1838
|
-
if (await access(
|
|
1839
|
-
await cp(
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
{
|
|
1843
|
-
recursive: true
|
|
1844
|
-
}
|
|
1845
|
-
);
|
|
1868
|
+
if (await access(join(viewsFolderPath, folder)).then(() => true).catch(() => false)) {
|
|
1869
|
+
await cp(join(viewsFolderPath, folder), join(outputPath, folder), {
|
|
1870
|
+
recursive: true
|
|
1871
|
+
});
|
|
1846
1872
|
}
|
|
1847
1873
|
}
|
|
1848
1874
|
if (config.hasGtfsRealtime) {
|
|
1849
1875
|
await copyFile(
|
|
1850
1876
|
"node_modules/pbf/dist/pbf.js",
|
|
1851
|
-
|
|
1877
|
+
join(outputPath, "js/pbf.js")
|
|
1852
1878
|
);
|
|
1853
1879
|
await copyFile(
|
|
1854
1880
|
"node_modules/gtfs-realtime-pbf-js-module/gtfs-realtime.browser.proto.js",
|
|
1855
|
-
|
|
1881
|
+
join(outputPath, "js/gtfs-realtime.browser.proto.js")
|
|
1856
1882
|
);
|
|
1857
1883
|
}
|
|
1858
1884
|
}
|
|
1859
|
-
function zipFolder(
|
|
1860
|
-
const output = createWriteStream(
|
|
1885
|
+
function zipFolder(outputPath) {
|
|
1886
|
+
const output = createWriteStream(join(outputPath, "timetables.zip"));
|
|
1861
1887
|
const archive = archiver("zip");
|
|
1862
|
-
return new Promise((
|
|
1863
|
-
output.on("close",
|
|
1888
|
+
return new Promise((resolve2, reject) => {
|
|
1889
|
+
output.on("close", resolve2);
|
|
1864
1890
|
archive.on("error", reject);
|
|
1865
1891
|
archive.pipe(output);
|
|
1866
1892
|
archive.glob("**/*.{txt,css,js,html}", {
|
|
1867
|
-
cwd:
|
|
1893
|
+
cwd: outputPath
|
|
1868
1894
|
});
|
|
1869
1895
|
archive.finalize();
|
|
1870
1896
|
});
|
|
@@ -1888,7 +1914,7 @@ function generateFolderName(timetablePage) {
|
|
|
1888
1914
|
return sanitize(`${timetable.start_date}-${timetable.end_date}`);
|
|
1889
1915
|
}
|
|
1890
1916
|
async function renderTemplate(templateFileName, templateVars, config) {
|
|
1891
|
-
const templatePath =
|
|
1917
|
+
const templatePath = getPathToTemplateFile(templateFileName, config);
|
|
1892
1918
|
const html = await renderFile(templatePath, {
|
|
1893
1919
|
_,
|
|
1894
1920
|
md: (text) => insane(marked.parseInline(text)),
|
|
@@ -2072,12 +2098,13 @@ function progressBar(formatString, barTotal, config) {
|
|
|
2072
2098
|
}
|
|
2073
2099
|
|
|
2074
2100
|
// src/lib/gtfs-to-html.ts
|
|
2075
|
-
import
|
|
2101
|
+
import path from "node:path";
|
|
2076
2102
|
import { mkdir as mkdir2, writeFile } from "node:fs/promises";
|
|
2077
2103
|
import { map } from "lodash-es";
|
|
2078
2104
|
import { openDb as openDb2, importGtfs } from "gtfs";
|
|
2079
2105
|
import sanitize2 from "sanitize-filename";
|
|
2080
2106
|
import Timer from "timer-machine";
|
|
2107
|
+
import untildify2 from "untildify";
|
|
2081
2108
|
var gtfsToHtml = async (initialConfig) => {
|
|
2082
2109
|
const config = setDefaultConfig(initialConfig);
|
|
2083
2110
|
const timer = new Timer();
|
|
@@ -2104,8 +2131,8 @@ var gtfsToHtml = async (initialConfig) => {
|
|
|
2104
2131
|
const agencyKey = config.agencies.map(
|
|
2105
2132
|
(agency) => agency.agencyKey ?? agency.agency_key ?? "unknown"
|
|
2106
2133
|
).join("-");
|
|
2107
|
-
const
|
|
2108
|
-
const
|
|
2134
|
+
const outputPath = config.outputPath ? untildify2(config.outputPath) : path.join(process.cwd(), "html", sanitize2(agencyKey));
|
|
2135
|
+
const stats = {
|
|
2109
2136
|
timetables: 0,
|
|
2110
2137
|
timetablePages: 0,
|
|
2111
2138
|
calendars: 0,
|
|
@@ -2119,9 +2146,9 @@ var gtfsToHtml = async (initialConfig) => {
|
|
|
2119
2146
|
getTimetablePagesForAgency(config),
|
|
2120
2147
|
"timetable_page_id"
|
|
2121
2148
|
);
|
|
2122
|
-
await prepDirectory(
|
|
2149
|
+
await prepDirectory(outputPath);
|
|
2123
2150
|
if (config.noHead !== true && ["html", "pdf"].includes(config.outputFormat)) {
|
|
2124
|
-
await copyStaticAssets(config,
|
|
2151
|
+
await copyStaticAssets(config, outputPath);
|
|
2125
2152
|
}
|
|
2126
2153
|
const bar = progressBar(
|
|
2127
2154
|
`${agencyKey}: Generating ${config.outputFormat.toUpperCase()} timetables {bar} {value}/{total}`,
|
|
@@ -2136,7 +2163,7 @@ var gtfsToHtml = async (initialConfig) => {
|
|
|
2136
2163
|
);
|
|
2137
2164
|
for (const timetable of timetablePage.timetables) {
|
|
2138
2165
|
for (const warning of timetable.warnings) {
|
|
2139
|
-
|
|
2166
|
+
stats.warnings.push(warning);
|
|
2140
2167
|
bar?.interrupt(warning);
|
|
2141
2168
|
}
|
|
2142
2169
|
}
|
|
@@ -2145,20 +2172,20 @@ var gtfsToHtml = async (initialConfig) => {
|
|
|
2145
2172
|
`No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`
|
|
2146
2173
|
);
|
|
2147
2174
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2175
|
+
stats.timetables += timetablePage.consolidatedTimetables.length;
|
|
2176
|
+
stats.timetablePages += 1;
|
|
2150
2177
|
const datePath = generateFolderName(timetablePage);
|
|
2151
|
-
await mkdir2(
|
|
2178
|
+
await mkdir2(path.join(outputPath, datePath), { recursive: true });
|
|
2152
2179
|
config.assetPath = "../";
|
|
2153
|
-
timetablePage.relativePath =
|
|
2180
|
+
timetablePage.relativePath = path.join(
|
|
2154
2181
|
datePath,
|
|
2155
2182
|
sanitize2(timetablePage.filename)
|
|
2156
2183
|
);
|
|
2157
2184
|
if (config.outputFormat === "csv") {
|
|
2158
2185
|
for (const timetable of timetablePage.consolidatedTimetables) {
|
|
2159
2186
|
const csv = await generateTimetableCSV(timetable);
|
|
2160
|
-
const csvPath =
|
|
2161
|
-
|
|
2187
|
+
const csvPath = path.join(
|
|
2188
|
+
outputPath,
|
|
2162
2189
|
datePath,
|
|
2163
2190
|
generateFileName(timetable, config, "csv")
|
|
2164
2191
|
);
|
|
@@ -2166,8 +2193,8 @@ var gtfsToHtml = async (initialConfig) => {
|
|
|
2166
2193
|
}
|
|
2167
2194
|
} else {
|
|
2168
2195
|
const html = await generateTimetableHTML(timetablePage, config);
|
|
2169
|
-
const htmlPath =
|
|
2170
|
-
|
|
2196
|
+
const htmlPath = path.join(
|
|
2197
|
+
outputPath,
|
|
2171
2198
|
datePath,
|
|
2172
2199
|
sanitize2(timetablePage.filename)
|
|
2173
2200
|
);
|
|
@@ -2178,12 +2205,12 @@ var gtfsToHtml = async (initialConfig) => {
|
|
|
2178
2205
|
}
|
|
2179
2206
|
timetablePages.push(timetablePage);
|
|
2180
2207
|
const timetableStats = generateStats(timetablePage);
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2208
|
+
stats.stops += timetableStats.stops;
|
|
2209
|
+
stats.routes += timetableStats.routes;
|
|
2210
|
+
stats.trips += timetableStats.trips;
|
|
2211
|
+
stats.calendars += timetableStats.calendars;
|
|
2185
2212
|
} catch (error) {
|
|
2186
|
-
|
|
2213
|
+
stats.warnings.push(error?.message);
|
|
2187
2214
|
bar?.interrupt(error.message);
|
|
2188
2215
|
}
|
|
2189
2216
|
bar?.increment();
|
|
@@ -2191,26 +2218,27 @@ var gtfsToHtml = async (initialConfig) => {
|
|
|
2191
2218
|
if (config.outputFormat === "html") {
|
|
2192
2219
|
config.assetPath = "";
|
|
2193
2220
|
const html = await generateOverviewHTML(timetablePages, config);
|
|
2194
|
-
await writeFile(
|
|
2221
|
+
await writeFile(path.join(outputPath, "index.html"), html);
|
|
2195
2222
|
}
|
|
2196
|
-
const logText = generateLogText(
|
|
2197
|
-
await writeFile(
|
|
2223
|
+
const logText = generateLogText(stats, config);
|
|
2224
|
+
await writeFile(path.join(outputPath, "log.txt"), logText);
|
|
2198
2225
|
if (config.zipOutput) {
|
|
2199
|
-
await zipFolder(
|
|
2226
|
+
await zipFolder(outputPath);
|
|
2200
2227
|
}
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2228
|
+
const fullOutputPath = path.join(
|
|
2229
|
+
outputPath,
|
|
2203
2230
|
config.zipOutput ? "/timetables.zip" : ""
|
|
2204
2231
|
);
|
|
2205
2232
|
config.log(
|
|
2206
|
-
`${agencyKey}: ${config.outputFormat.toUpperCase()} timetables created at ${
|
|
2233
|
+
`${agencyKey}: ${config.outputFormat.toUpperCase()} timetables created at ${fullOutputPath}`
|
|
2207
2234
|
);
|
|
2208
|
-
logStats(
|
|
2235
|
+
logStats(stats, config);
|
|
2209
2236
|
const seconds = Math.round(timer.time() / 1e3);
|
|
2210
2237
|
config.log(
|
|
2211
2238
|
`${agencyKey}: ${config.outputFormat.toUpperCase()} timetable generation required ${seconds} seconds`
|
|
2212
2239
|
);
|
|
2213
2240
|
timer.stop();
|
|
2241
|
+
return fullOutputPath;
|
|
2214
2242
|
};
|
|
2215
2243
|
var gtfs_to_html_default = gtfsToHtml;
|
|
2216
2244
|
|