@rr0/cms 0.3.16 → 0.3.17
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/{BuildContext.d.ts → CMSContext.d.ts} +1 -1
- package/dist/{RR0Build.d.ts → CMSGenerator.d.ts} +30 -10
- package/dist/{RR0Build.js → CMSGenerator.js} +92 -91
- package/dist/CMSGenerator.test.d.ts +1 -0
- package/dist/{RR0Build.test.js → CMSGenerator.test.js} +39 -27
- package/dist/{DefaultContentVisitor.d.ts → DataContentVisitor.d.ts} +4 -1
- package/dist/{DefaultContentVisitor.js → DataContentVisitor.js} +4 -1
- package/dist/OpenGraphCommand.js +1 -1
- package/dist/RR0Context.d.ts +4 -1
- package/dist/RR0Context.js +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/org/eu/fr/cnes/geipan/geipan/GeipanFileDatasource.d.ts +1 -1
- package/dist/org/eu/fr/cnes/geipan/geipan/GeipanRR0Mapping.d.ts +2 -2
- package/dist/search/SearchVisitor.js +1 -2
- package/dist/test/RR0TestUtil.d.ts +2 -2
- package/dist/test/RR0TestUtil.js +1 -1
- package/dist/time/SsiTitleReplaceCommand.d.ts +4 -0
- package/dist/time/SsiTitleReplaceCommand.js +16 -11
- package/dist/time/TimeDirectoryStep.d.ts +46 -0
- package/dist/time/TimeDirectoryStep.js +88 -0
- package/dist/time/TimeDirectoryStep.test.js +36 -0
- package/dist/time/TimeService.d.ts +1 -3
- package/dist/time/TimeService.js +4 -11
- package/dist/time/datasource/CsvFileSource.d.ts +1 -1
- package/dist/time/datasource/CsvMapper.d.ts +5 -3
- package/dist/time/datasource/CsvMapper.js +17 -12
- package/dist/time/datasource/CsvMapper.test.js +29 -0
- package/dist/time/datasource/FileSource.d.ts +1 -1
- package/dist/time/datasource/FileSource.js +1 -0
- package/dist/time/datasource/baseovnifrance/BaseOvniFranceRR0Mapping.d.ts +2 -2
- package/dist/time/datasource/fufora/FuforaRR0Mapping.d.ts +2 -2
- package/dist/time/datasource/nuforc/NuforcRR0Mapping.d.ts +2 -2
- package/dist/time/datasource/rr0/RR0CaseMapping.d.ts +2 -2
- package/dist/time/datasource/rr0/RR0Datasource.d.ts +0 -3
- package/dist/time/datasource/rr0/RR0Datasource.js +3 -6
- package/dist/time/datasource/rr0/RR0Datasource.test.d.ts +2 -2
- package/dist/time/datasource/rr0/RR0Datasource.test.js +4 -2
- package/dist/time/datasource/rr0/RR0FileDatasource.d.ts +1 -1
- package/dist/time/datasource/rr0/RR0FileDatasource.test.d.ts +1 -0
- package/dist/time/datasource/rr0/RR0FileDatasource.test.js +14 -0
- package/dist/time/datasource/rr0/RR0Mapping.d.ts +2 -2
- package/dist/time/datasource/sceau/SceauDatasource.test.d.ts +2 -2
- package/dist/time/datasource/sceau/SceauRR0Mapping.d.ts +2 -2
- package/dist/time/datasource/ufo-search/UfoSearchMapping.d.ts +2 -2
- package/dist/time/datasource/urecat/UrecatRR0Mapping.d.ts +2 -2
- package/dist/time/html/TimeRenderer.js +3 -2
- package/dist/time/html/TimeReplacer.test.js +0 -1
- package/package.json +3 -3
- /package/dist/{BuildContext.js → CMSContext.js} +0 -0
- /package/dist/{RR0Build.test.d.ts → time/TimeDirectoryStep.test.d.ts} +0 -0
|
@@ -5,8 +5,8 @@ import { CaseFactory } from "../science/index.js";
|
|
|
5
5
|
import { TimeTestUtil } from "../time/TimeTestUtil";
|
|
6
6
|
import { AllDataService, PeopleFactory } from "@rr0/data";
|
|
7
7
|
import { CountryService } from "../org/country/CountryService";
|
|
8
|
-
import {
|
|
9
|
-
export declare class RR0TestUtil implements
|
|
8
|
+
import { CMSContext } from "../CMSContext";
|
|
9
|
+
export declare class RR0TestUtil implements CMSContext {
|
|
10
10
|
readonly rootDir: string;
|
|
11
11
|
readonly outDir: string;
|
|
12
12
|
readonly config: FileWriteConfig;
|
package/dist/test/RR0TestUtil.js
CHANGED
|
@@ -28,7 +28,7 @@ export class RR0TestUtil {
|
|
|
28
28
|
timeZoneName: "short"
|
|
29
29
|
};
|
|
30
30
|
const eventFactory = new RR0EventFactory();
|
|
31
|
-
const sightingFactory = new EventDataFactory(eventFactory, "sighting", ["index"]);
|
|
31
|
+
const sightingFactory = new EventDataFactory(eventFactory, ["sighting"], ["index"]);
|
|
32
32
|
const orgFactory = this.orgFactory = new CmsOrganizationFactory(eventFactory);
|
|
33
33
|
this.orgService = new OrganizationService([], "org", orgFactory, undefined);
|
|
34
34
|
this.caseFactory = new CaseFactory(eventFactory);
|
|
@@ -6,6 +6,10 @@ import { HtmlRR0Context } from "../RR0Context.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class SsiTitleReplaceCommand extends SsiEchoVarReplaceCommand {
|
|
8
8
|
protected defaultHandlers: StringContextHandler[];
|
|
9
|
+
/**
|
|
10
|
+
* @param defaultHandlers Will generate a title for a given context/file, if no title is found.
|
|
11
|
+
*/
|
|
9
12
|
constructor(defaultHandlers?: StringContextHandler[]);
|
|
10
13
|
protected createReplacer(context: HtmlRR0Context): Promise<RegexReplacer>;
|
|
14
|
+
protected getTitle(context: HtmlRR0Context): string;
|
|
11
15
|
}
|
|
@@ -4,6 +4,9 @@ import { SsiEchoVarReplaceCommand } from "ssg-api";
|
|
|
4
4
|
* with a link if there's a <meta name="url"> content.
|
|
5
5
|
*/
|
|
6
6
|
export class SsiTitleReplaceCommand extends SsiEchoVarReplaceCommand {
|
|
7
|
+
/**
|
|
8
|
+
* @param defaultHandlers Will generate a title for a given context/file, if no title is found.
|
|
9
|
+
*/
|
|
7
10
|
constructor(defaultHandlers = []) {
|
|
8
11
|
super("title");
|
|
9
12
|
this.defaultHandlers = defaultHandlers;
|
|
@@ -11,18 +14,20 @@ export class SsiTitleReplaceCommand extends SsiEchoVarReplaceCommand {
|
|
|
11
14
|
async createReplacer(context) {
|
|
12
15
|
return {
|
|
13
16
|
replace: (_match, ..._args) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
this.defaultHandlers.some(handle => !title && (title = handle(context)));
|
|
18
|
-
}
|
|
19
|
-
if (!title) {
|
|
20
|
-
title = inputFile.name;
|
|
21
|
-
}
|
|
22
|
-
inputFile.title = title;
|
|
23
|
-
const titleUrl = inputFile.meta.url;
|
|
24
|
-
return titleUrl ? `<a href="${titleUrl}" target="_blank">${title}</a>` : title;
|
|
17
|
+
context.file.title = this.getTitle(context);
|
|
18
|
+
const titleUrl = context.file.meta.url;
|
|
19
|
+
return titleUrl ? `<a href="${titleUrl}" target="_blank">${(this.getTitle(context))}</a>` : this.getTitle(context);
|
|
25
20
|
}
|
|
26
21
|
};
|
|
27
22
|
}
|
|
23
|
+
getTitle(context) {
|
|
24
|
+
let title = context.file.title;
|
|
25
|
+
if (!title) {
|
|
26
|
+
this.defaultHandlers.some(handle => !title && (title = handle(context)));
|
|
27
|
+
}
|
|
28
|
+
if (!title) {
|
|
29
|
+
title = context.file.name;
|
|
30
|
+
}
|
|
31
|
+
return title;
|
|
32
|
+
}
|
|
28
33
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { DirectoryStep, FileWriteConfig, OutputFunc } from "ssg-api";
|
|
2
|
+
import { TimeService } from "./TimeService.js";
|
|
3
|
+
import { HtmlRR0Context, RR0Context } from "../RR0Context";
|
|
4
|
+
import { RR0Event } from "@rr0/data";
|
|
5
|
+
import { TimeElementFactory } from "./html";
|
|
6
|
+
/**
|
|
7
|
+
* Builds a directory page for UFO times.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TimeDirectoryStep extends DirectoryStep {
|
|
10
|
+
protected service: TimeService;
|
|
11
|
+
protected elementFactory: TimeElementFactory;
|
|
12
|
+
protected outputFunc: OutputFunc;
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param service
|
|
16
|
+
* @param elementFactory
|
|
17
|
+
* @param rootDirs The directories where UFO times info can be found.
|
|
18
|
+
* @param excludedDirs The directories to exclude from the UFO time directory search.
|
|
19
|
+
* @param templateFileName The template of the directory page to build.
|
|
20
|
+
* @param outputFunc
|
|
21
|
+
* @param config
|
|
22
|
+
*/
|
|
23
|
+
constructor(service: TimeService, elementFactory: TimeElementFactory, rootDirs: string[], excludedDirs: string[], templateFileName: string, outputFunc: OutputFunc, config: FileWriteConfig);
|
|
24
|
+
/**
|
|
25
|
+
* Convert an array of Time[] to an <ul> HTML unordered list.
|
|
26
|
+
*
|
|
27
|
+
* @param context
|
|
28
|
+
* @param events
|
|
29
|
+
*/
|
|
30
|
+
protected toList(context: HtmlRR0Context, events: RR0Event[]): HTMLUListElement;
|
|
31
|
+
/**
|
|
32
|
+
* Convert a Time object to an HTML list item.
|
|
33
|
+
*
|
|
34
|
+
* @param context
|
|
35
|
+
* @param event
|
|
36
|
+
*/
|
|
37
|
+
protected toListItem(context: HtmlRR0Context, event: RR0Event): HTMLLIElement;
|
|
38
|
+
protected processDirs(context: HtmlRR0Context, dirNames: string[]): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Read time JSON files contents and instantiate them as Time objects.
|
|
41
|
+
*
|
|
42
|
+
* @param context
|
|
43
|
+
* @param dirNames The directories to look for time.json files.
|
|
44
|
+
*/
|
|
45
|
+
protected scan(context: RR0Context, dirNames: string[]): Promise<RR0Event[]>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { DirectoryStep } from "ssg-api";
|
|
2
|
+
import { StringUtil } from "../util";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a directory page for UFO times.
|
|
5
|
+
*/
|
|
6
|
+
export class TimeDirectoryStep extends DirectoryStep {
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param service
|
|
10
|
+
* @param elementFactory
|
|
11
|
+
* @param rootDirs The directories where UFO times info can be found.
|
|
12
|
+
* @param excludedDirs The directories to exclude from the UFO time directory search.
|
|
13
|
+
* @param templateFileName The template of the directory page to build.
|
|
14
|
+
* @param outputFunc
|
|
15
|
+
* @param config
|
|
16
|
+
*/
|
|
17
|
+
constructor(service, elementFactory, rootDirs, excludedDirs, templateFileName, outputFunc, config) {
|
|
18
|
+
super({ rootDirs, excludedDirs, templateFileName, getOutputPath: config.getOutputPath }, "time directory");
|
|
19
|
+
this.service = service;
|
|
20
|
+
this.elementFactory = elementFactory;
|
|
21
|
+
this.outputFunc = outputFunc;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Convert an array of Time[] to an <ul> HTML unordered list.
|
|
25
|
+
*
|
|
26
|
+
* @param context
|
|
27
|
+
* @param events
|
|
28
|
+
*/
|
|
29
|
+
toList(context, events) {
|
|
30
|
+
const listItems = events.map(event => {
|
|
31
|
+
if (!event.title) {
|
|
32
|
+
const lastSlash = event.dirName.lastIndexOf("/");
|
|
33
|
+
const lastDir = event.dirName.substring(lastSlash + 1);
|
|
34
|
+
event.title = StringUtil.camelToText(lastDir);
|
|
35
|
+
}
|
|
36
|
+
return this.toListItem(context, event);
|
|
37
|
+
});
|
|
38
|
+
const ul = context.file.document.createElement("ul");
|
|
39
|
+
ul.append(...listItems);
|
|
40
|
+
ul.className = "links";
|
|
41
|
+
return ul;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert a Time object to an HTML list item.
|
|
45
|
+
*
|
|
46
|
+
* @param context
|
|
47
|
+
* @param event
|
|
48
|
+
*/
|
|
49
|
+
toListItem(context, event) {
|
|
50
|
+
const item = context.file.document.createElement("li");
|
|
51
|
+
const eventContext = context.clone();
|
|
52
|
+
this.service.setContextFromFile(eventContext, event.dirName);
|
|
53
|
+
if (event.time) {
|
|
54
|
+
eventContext.time.date = event.time;
|
|
55
|
+
}
|
|
56
|
+
const ref = this.elementFactory.create(eventContext, context, { url: true, contentOnly: true });
|
|
57
|
+
item.appendChild(ref);
|
|
58
|
+
return item;
|
|
59
|
+
}
|
|
60
|
+
async processDirs(context, dirNames) {
|
|
61
|
+
const events = await this.scan(context, dirNames);
|
|
62
|
+
const ul = this.toList(context, events);
|
|
63
|
+
const outputPath = this.config.getOutputPath(context);
|
|
64
|
+
const output = context.newOutput(outputPath);
|
|
65
|
+
output.contents = context.file.contents.replace(`<!--#echo var="directories" -->`, ul.outerHTML);
|
|
66
|
+
await this.outputFunc(context, output);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Read time JSON files contents and instantiate them as Time objects.
|
|
70
|
+
*
|
|
71
|
+
* @param context
|
|
72
|
+
* @param dirNames The directories to look for time.json files.
|
|
73
|
+
*/
|
|
74
|
+
async scan(context, dirNames) {
|
|
75
|
+
const events = [];
|
|
76
|
+
for (const dirName of dirNames) {
|
|
77
|
+
try {
|
|
78
|
+
const dirEvents = await this.service.get(dirName);
|
|
79
|
+
events.push(...dirEvents);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
context.warn(`${dirName} has no event.json description`);
|
|
83
|
+
// No json, just guess title.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return events;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from "@javarome/testscript";
|
|
2
|
+
import { TimeDirectoryStep } from "./TimeDirectoryStep.js";
|
|
3
|
+
import { rr0TestUtil, testFilePath } from "../test";
|
|
4
|
+
import { getTimeFiles } from "../CMSGenerator.test";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { TimeService } from "./TimeService";
|
|
7
|
+
import { AllDataService, RR0EventFactory } from "@rr0/data";
|
|
8
|
+
import { TimeElementFactory } from "./html";
|
|
9
|
+
describe("TimeDirectoryStep", () => {
|
|
10
|
+
async function outputFunc(context, info, outDir = rr0TestUtil.outDir + "/") {
|
|
11
|
+
info.name = `${outDir}${info.name}`;
|
|
12
|
+
}
|
|
13
|
+
test("directory", async () => {
|
|
14
|
+
const template = `
|
|
15
|
+
<!--#include virtual="/header-start.html" -->
|
|
16
|
+
<title>16ème siècle</title>
|
|
17
|
+
<!--#include virtual="/header-end.html" -->
|
|
18
|
+
<p>Before</p>
|
|
19
|
+
<!--#echo var="directories" -->
|
|
20
|
+
<p>Le XVIIIᵉ siècle est celui des "Lumières".</p>
|
|
21
|
+
<!--#include virtual="/footer.html" -->
|
|
22
|
+
`;
|
|
23
|
+
const timeRoot = rr0TestUtil.time.timeOptions.rootDir;
|
|
24
|
+
const timeOptions = { rootDir: timeRoot, files: await getTimeFiles() };
|
|
25
|
+
const dataService = new AllDataService([new RR0EventFactory()]);
|
|
26
|
+
const timeService = new TimeService(dataService, timeOptions);
|
|
27
|
+
const timesDirectoryPath = testFilePath("time/0/0/6/5/index.html");
|
|
28
|
+
const context = rr0TestUtil.newContext(timesDirectoryPath, template);
|
|
29
|
+
const ufoTimesExclusions = [];
|
|
30
|
+
const timeDirs = timeService.files.map(timePath => path.dirname(timePath));
|
|
31
|
+
const timeElementFactory = new TimeElementFactory(rr0TestUtil.time.timeRenderer);
|
|
32
|
+
const step = new TimeDirectoryStep(timeService, timeElementFactory, timeDirs, ufoTimesExclusions, timesDirectoryPath, outputFunc, rr0TestUtil.config);
|
|
33
|
+
const stepResult = await step.execute(context);
|
|
34
|
+
expect(stepResult.directoryCount).toBe(22);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -11,7 +11,5 @@ export declare class TimeService extends AbstractDataService<RR0Event, RR0EventJ
|
|
|
11
11
|
parseFileName(fileName: string): RegExpExecArray | null;
|
|
12
12
|
titleFromFile(context: HtmlRR0Context, fileName: string, timeTextBuilder: TimeTextBuilder): string | undefined;
|
|
13
13
|
contextFromFileName(context: HtmlRR0Context, fileName?: string): TimeContext | undefined;
|
|
14
|
-
|
|
15
|
-
setContextFromFile(context: HtmlRR0Context, filePath: string): void;
|
|
16
|
-
protected setTimeFromPath(context: HtmlRR0Context, filePath: string): void;
|
|
14
|
+
setContextFromFile(context: HtmlRR0Context, filePath: string): TimeContext | undefined;
|
|
17
15
|
}
|
package/dist/time/TimeService.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { AbstractDataService } from "@rr0/data";
|
|
1
|
+
import { AbstractDataService, EventDataFactory, RR0EventFactory } from "@rr0/data";
|
|
2
2
|
import { RR0ContextImpl } from "../RR0Context";
|
|
3
3
|
import { StringUtil } from "../util";
|
|
4
4
|
export class TimeService extends AbstractDataService {
|
|
5
5
|
constructor(dataService, options, timePathRegex = TimeService.defaultRegex) {
|
|
6
|
-
super(dataService,
|
|
6
|
+
super(dataService, new EventDataFactory(new RR0EventFactory(), ["birth", "death", "image", "book", "article", "sighting", "nationality", "move", "occupation"], options.files), options.files);
|
|
7
7
|
this.options = options;
|
|
8
8
|
this.timePathRegex = timePathRegex;
|
|
9
9
|
}
|
|
@@ -14,7 +14,7 @@ export class TimeService extends AbstractDataService {
|
|
|
14
14
|
let title;
|
|
15
15
|
const timeContext = this.contextFromFileName(context, fileName);
|
|
16
16
|
if (timeContext) {
|
|
17
|
-
const pageContext = new RR0ContextImpl(context.locale, timeContext, context.config, context.people, context.file);
|
|
17
|
+
const pageContext = new RR0ContextImpl(context.locale, timeContext, context.config, context.people, context.file, context.messages, context.cms);
|
|
18
18
|
title = timeTextBuilder.build(pageContext);
|
|
19
19
|
title = StringUtil.capitalizeFirstLetter(title);
|
|
20
20
|
}
|
|
@@ -47,7 +47,7 @@ export class TimeService extends AbstractDataService {
|
|
|
47
47
|
}
|
|
48
48
|
return timeContext;
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
setContextFromFile(context, filePath) {
|
|
51
51
|
const time = context.time;
|
|
52
52
|
time.reset();
|
|
53
53
|
const newTimeContext = this.contextFromFileName(context, filePath);
|
|
@@ -59,12 +59,5 @@ export class TimeService extends AbstractDataService {
|
|
|
59
59
|
}
|
|
60
60
|
return newTimeContext;
|
|
61
61
|
}
|
|
62
|
-
setContextFromFile(context, filePath) {
|
|
63
|
-
this.setTimeFromPath(context, filePath);
|
|
64
|
-
}
|
|
65
|
-
setTimeFromPath(context, filePath) {
|
|
66
|
-
context.time.reset(); // Don't use time context from previous page.
|
|
67
|
-
this.gSetTimeFromPath(context, filePath);
|
|
68
|
-
}
|
|
69
62
|
}
|
|
70
63
|
TimeService.defaultRegex = /time\/(-)?(\d)\/(\d)\/(\d)\/(\d)\/?(\d{2})?\/?(\d{2})?\/?(index(_[a-z]{2})?.html)?/;
|
|
@@ -15,5 +15,5 @@ export declare class CsvFileSource<S> extends FileSource {
|
|
|
15
15
|
* @protected
|
|
16
16
|
*/
|
|
17
17
|
fileName(context: HtmlSsgContext, datasource: Datasource<S>): string;
|
|
18
|
-
write(context: HtmlRR0Context, datasourceCases: S[], fetchTime: Date, datasource: Datasource<any>):
|
|
18
|
+
write(context: HtmlRR0Context, datasourceCases: S[], fetchTime: Date, datasource: Datasource<any>): string;
|
|
19
19
|
}
|
|
@@ -4,17 +4,19 @@ export declare class CsvMapper<S> implements CaseMapper<RR0Context, S, string> {
|
|
|
4
4
|
readonly sep: string;
|
|
5
5
|
readonly escapeStr: string;
|
|
6
6
|
readonly prefix: string;
|
|
7
|
+
protected maxLevel: number;
|
|
7
8
|
readonly fields: Set<string>;
|
|
8
|
-
constructor(sep?: string, escapeStr?: string, prefix?: string);
|
|
9
|
-
readonly fieldMapper: (context: RR0Context, key: string, value: any, sourceTime: Date) => string;
|
|
9
|
+
constructor(sep?: string, escapeStr?: string, prefix?: string, maxLevel?: number);
|
|
10
|
+
readonly fieldMapper: (context: RR0Context, key: string, value: any, sourceTime: Date, level?: number) => string;
|
|
10
11
|
/**
|
|
11
12
|
* Map a case to a CSV row.
|
|
12
13
|
*
|
|
13
14
|
* @param context
|
|
14
15
|
* @param sourceCase
|
|
15
16
|
* @param sourceTime
|
|
17
|
+
* @param level
|
|
16
18
|
*/
|
|
17
|
-
map(context: RR0Context, sourceCase: S, sourceTime: Date): string;
|
|
19
|
+
map(context: RR0Context, sourceCase: S, sourceTime: Date, level?: number): string;
|
|
18
20
|
/**
|
|
19
21
|
* Reduce a set of cases to a CSV string.
|
|
20
22
|
*
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Level2Date as EdtfDate } from "@rr0/time";
|
|
2
2
|
export class CsvMapper {
|
|
3
|
-
constructor(sep = ",", escapeStr = "\"", prefix = "") {
|
|
3
|
+
constructor(sep = ",", escapeStr = "\"", prefix = "", maxLevel = 1) {
|
|
4
4
|
this.sep = sep;
|
|
5
5
|
this.escapeStr = escapeStr;
|
|
6
6
|
this.prefix = prefix;
|
|
7
|
+
this.maxLevel = maxLevel;
|
|
7
8
|
this.fields = new Set();
|
|
8
|
-
this.fieldMapper = (context, key, value, sourceTime) => {
|
|
9
|
+
this.fieldMapper = (context, key, value, sourceTime, level = 0) => {
|
|
9
10
|
let addField = true;
|
|
10
11
|
let val;
|
|
11
12
|
if (value instanceof Date) {
|
|
@@ -18,17 +19,19 @@ export class CsvMapper {
|
|
|
18
19
|
val = value.toString();
|
|
19
20
|
}
|
|
20
21
|
else if (Array.isArray(value)) {
|
|
21
|
-
val = this.escape(value.map((item, i) => this.fieldMapper(context, String(i), item, sourceTime)).join(this.sep), true);
|
|
22
|
+
val = this.escape(value.map((item, i) => this.fieldMapper(context, String(i), item, sourceTime, level + 1)).join(this.sep), true);
|
|
22
23
|
}
|
|
23
24
|
else if (typeof value === "object") {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
if (level <= this.maxLevel) {
|
|
26
|
+
const subMapper = new CsvMapper(this.sep, this.escapeStr, this.prefix + key + ".", level);
|
|
27
|
+
const subValues = subMapper.map(context, value, sourceTime, level + 1);
|
|
28
|
+
let addSubFields = !isFinite(key);
|
|
29
|
+
if (addSubFields) {
|
|
30
|
+
subMapper.fields.forEach(subField => this.fields.add(subField));
|
|
31
|
+
}
|
|
32
|
+
val = subValues;
|
|
29
33
|
}
|
|
30
34
|
addField = false;
|
|
31
|
-
val = subValues;
|
|
32
35
|
}
|
|
33
36
|
else {
|
|
34
37
|
val = value;
|
|
@@ -45,10 +48,12 @@ export class CsvMapper {
|
|
|
45
48
|
* @param context
|
|
46
49
|
* @param sourceCase
|
|
47
50
|
* @param sourceTime
|
|
51
|
+
* @param level
|
|
48
52
|
*/
|
|
49
|
-
map(context, sourceCase, sourceTime) {
|
|
50
|
-
const
|
|
51
|
-
|
|
53
|
+
map(context, sourceCase, sourceTime, level = 0) {
|
|
54
|
+
const sourceCaseEntries = Object.entries(sourceCase);
|
|
55
|
+
const entries = Array.from(sourceCaseEntries).sort((entry1, entry2) => entry1[0].localeCompare(entry2[0]));
|
|
56
|
+
return entries.map(entry => this.fieldMapper(context, entry[0], entry[1], sourceTime, level)).join(this.sep);
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
59
|
* Reduce a set of cases to a CSV string.
|
|
@@ -32,6 +32,35 @@ describe("CsvMapper", () => {
|
|
|
32
32
|
.join("\n");
|
|
33
33
|
expect(csvContents).toBe(expectedCsv);
|
|
34
34
|
});
|
|
35
|
+
describe("mapper", () => {
|
|
36
|
+
const date = new Date("2025-01-01");
|
|
37
|
+
const context = rr0TestUtil.newHtmlContext("time/1/9/7/0/03/index.html");
|
|
38
|
+
test("string", () => {
|
|
39
|
+
const mapper = new CsvMapper(";");
|
|
40
|
+
expect(mapper.fieldMapper(context, "key1", "val1", date)).toBe("val1");
|
|
41
|
+
expect(Array.from(mapper.fields)).toEqual(["key1"]);
|
|
42
|
+
});
|
|
43
|
+
describe("object", () => {
|
|
44
|
+
test("level 0", () => {
|
|
45
|
+
const mapper = new CsvMapper(";");
|
|
46
|
+
const csvLine = mapper.fieldMapper(context, "obj1", { prop1: "propVal1", prop2: 12 }, date);
|
|
47
|
+
expect(Array.from(mapper.fields)).toEqual(["obj1.prop1", "obj1.prop2"]);
|
|
48
|
+
expect(csvLine).toBe("propVal1;12");
|
|
49
|
+
});
|
|
50
|
+
test("level 1", () => {
|
|
51
|
+
const mapper = new CsvMapper(";");
|
|
52
|
+
const csvLine = mapper.fieldMapper(context, "obj1", { prop1: "propVal1", prop2: 12, prop3: { prop31: "prop31Val" } }, date);
|
|
53
|
+
expect(Array.from(mapper.fields)).toEqual(["obj1.prop1", "obj1.prop2", "obj1.prop3.prop31"]);
|
|
54
|
+
expect(csvLine).toBe("propVal1;12;prop31Val");
|
|
55
|
+
});
|
|
56
|
+
test("level 2", () => {
|
|
57
|
+
const mapper = new CsvMapper(";");
|
|
58
|
+
const csvLine = mapper.fieldMapper(context, "obj1", { prop1: "propVal1", prop2: 12, prop3: { prop31: "prop31Val", prop4: { key4: "value4" } } }, date);
|
|
59
|
+
expect(Array.from(mapper.fields)).toEqual(["obj1.prop1", "obj1.prop2", "obj1.prop3.prop31"]);
|
|
60
|
+
expect(csvLine).toBe("propVal1;12;prop31Val");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
35
64
|
test("read", () => {
|
|
36
65
|
const fileMapper = new CsvMapper(";");
|
|
37
66
|
const fileName = path.join(process.cwd(), geipanFileDatasource.defaultFileName);
|
|
@@ -14,5 +14,5 @@ export declare class FileSource {
|
|
|
14
14
|
*/
|
|
15
15
|
fileName(context: HtmlSsgContext, datasource: Datasource<any>): string;
|
|
16
16
|
read(context: HtmlSsgContext, datasource: Datasource<any>): Promise<FileContents>;
|
|
17
|
-
writeContents(context: HtmlSsgContext, contents: string | NodeJS.ArrayBufferView, datasource: Datasource<any>):
|
|
17
|
+
writeContents(context: HtmlSsgContext, contents: string | NodeJS.ArrayBufferView, datasource: Datasource<any>): string;
|
|
18
18
|
}
|
|
@@ -23,6 +23,7 @@ export class FileSource {
|
|
|
23
23
|
writeContents(context, contents, datasource) {
|
|
24
24
|
const fileName = this.fileName(context, datasource);
|
|
25
25
|
fs.writeFileSync(fileName, contents, { encoding: this.encoding });
|
|
26
|
+
return fileName;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
FileSource.specialChars = /[ \-?!&*#().:\/\\;=°',]/g;
|
|
@@ -3,13 +3,13 @@ import { BaseOvniFranceCaseSummary } from "./BaseOvniFranceCaseSummary.js";
|
|
|
3
3
|
import { BaseOvniFranceHttpDatasource } from "./BaseOvniFranceHttpDatasource.js";
|
|
4
4
|
import { RR0CaseMapping } from "../rr0/index.js";
|
|
5
5
|
import { ChronologyReplacerActions } from "../ChronologyReplacerActions.js";
|
|
6
|
-
import {
|
|
6
|
+
import { CMSContext } from "../../../CMSContext";
|
|
7
7
|
export declare const baseOvniFranceDatasource: BaseOvniFranceHttpDatasource;
|
|
8
8
|
export declare class BaseOvniFranceRR0Mapping implements RR0CaseMapping<BaseOvniFranceCaseSummary> {
|
|
9
9
|
readonly actions: ChronologyReplacerActions;
|
|
10
10
|
datasource: BaseOvniFranceHttpDatasource;
|
|
11
11
|
mapper: BaseOvniFranceCaseSummaryRR0Mapper;
|
|
12
12
|
constructor(actions: ChronologyReplacerActions);
|
|
13
|
-
init(build:
|
|
13
|
+
init(build: CMSContext): this;
|
|
14
14
|
}
|
|
15
15
|
export declare const baseOvniFranceSortComparator: (c1: BaseOvniFranceCaseSummary, c2: BaseOvniFranceCaseSummary) => 0 | 1 | -1;
|
|
@@ -3,12 +3,12 @@ import { FuforaCaseSummaryRR0Mapper } from "./FuforaCaseSummaryRR0Mapper.js";
|
|
|
3
3
|
import { RR0CaseMapping } from "../rr0/RR0CaseMapping.js";
|
|
4
4
|
import { FuforaCaseSummary } from "./FuforaCaseSummary.js";
|
|
5
5
|
import { ChronologyReplacerActions } from "../ChronologyReplacerActions";
|
|
6
|
-
import {
|
|
6
|
+
import { CMSContext } from "../../../CMSContext";
|
|
7
7
|
export declare const fuforaDatasource: FuforaHttpDatasource;
|
|
8
8
|
export declare class FuforaRR0Mapping implements RR0CaseMapping<FuforaCaseSummary> {
|
|
9
9
|
readonly actions: ChronologyReplacerActions;
|
|
10
10
|
mapper: FuforaCaseSummaryRR0Mapper;
|
|
11
11
|
datasource: FuforaHttpDatasource;
|
|
12
12
|
constructor(actions: ChronologyReplacerActions);
|
|
13
|
-
init(build:
|
|
13
|
+
init(build: CMSContext): this;
|
|
14
14
|
}
|
|
@@ -3,12 +3,12 @@ import { NuforcHttpDatasource } from "./NuforcHttpDatasource.js";
|
|
|
3
3
|
import { RR0CaseMapping } from "../rr0";
|
|
4
4
|
import { ChronologyReplacerActions } from "../ChronologyReplacerActions";
|
|
5
5
|
import { NuforcCaseSummary } from "./NuforcCaseSummary";
|
|
6
|
-
import {
|
|
6
|
+
import { CMSContext } from "../../../CMSContext";
|
|
7
7
|
export declare const nuforcDatasource: NuforcHttpDatasource;
|
|
8
8
|
export declare class NuforcRR0Mapping implements RR0CaseMapping<NuforcCaseSummary> {
|
|
9
9
|
readonly actions: ChronologyReplacerActions;
|
|
10
10
|
datasource: NuforcHttpDatasource;
|
|
11
11
|
mapper: NuforcRR0Mapper;
|
|
12
12
|
constructor(actions: ChronologyReplacerActions);
|
|
13
|
-
init(build:
|
|
13
|
+
init(build: CMSContext): this;
|
|
14
14
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CaseMapping } from "../CaseMapping.js";
|
|
2
2
|
import { HtmlRR0Context } from "../../../RR0Context.js";
|
|
3
3
|
import { RR0CaseSummary } from "./RR0CaseSummary.js";
|
|
4
|
-
import {
|
|
4
|
+
import { CMSContext } from "../../../CMSContext";
|
|
5
5
|
/**
|
|
6
6
|
* Maps some datasource case to a RR0 case.
|
|
7
7
|
*
|
|
8
8
|
* @param S The source case type.
|
|
9
9
|
*/
|
|
10
10
|
export interface RR0CaseMapping<S> extends CaseMapping<HtmlRR0Context, S, RR0CaseSummary> {
|
|
11
|
-
init(build:
|
|
11
|
+
init(build: CMSContext): this;
|
|
12
12
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { RR0CaseSummary } from "./RR0CaseSummary.js";
|
|
2
2
|
import { AbstractDatasource } from "../AbstractDatasource.js";
|
|
3
|
-
import { TimeContextFilter } from "../TimeContextFilter.js";
|
|
4
3
|
import { Level2Date as EdtfDate } from "@rr0/time";
|
|
5
4
|
import { Place } from "@rr0/place";
|
|
6
|
-
export declare class RR0ContextFilter extends TimeContextFilter<RR0CaseSummary> {
|
|
7
|
-
}
|
|
8
5
|
export declare abstract class RR0Datasource extends AbstractDatasource<RR0CaseSummary> {
|
|
9
6
|
static idCount: number;
|
|
10
7
|
static readonly placeRegex: RegExp;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { AbstractDatasource } from "../AbstractDatasource.js";
|
|
2
|
-
import {
|
|
3
|
-
export class RR0ContextFilter extends TimeContextFilter {
|
|
4
|
-
}
|
|
2
|
+
import { StringUtil } from "../../../util";
|
|
5
3
|
export class RR0Datasource extends AbstractDatasource {
|
|
6
4
|
constructor() {
|
|
7
5
|
super(["Beau, Jérôme"], "RR0");
|
|
8
6
|
}
|
|
9
7
|
static id(dateTime, place) {
|
|
10
|
-
|
|
11
|
-
return `${(dateTime === null || dateTime === void 0 ? void 0 : dateTime.toString()) || ("rr0-" + ++this.idCount)}$${((_a = place === null || place === void 0 ? void 0 : place.toString()) !== null && _a !== void 0 ? _a : "")}`;
|
|
8
|
+
return `${(dateTime === null || dateTime === void 0 ? void 0 : dateTime.toString()) || ("rr0-" + ++this.idCount)}$${(place ? StringUtil.textToCamel(place.toString()) : "")}`;
|
|
12
9
|
}
|
|
13
10
|
}
|
|
14
11
|
RR0Datasource.idCount = 0;
|
|
15
|
-
RR0Datasource.placeRegex = /^(.+?)(?:\s*\((.+?)(?:\s*,\s*(.+?)
|
|
12
|
+
RR0Datasource.placeRegex = /^(.+?)(?:\s*\((.+?)(?:\s*,\s*(.+?)\s*,\s*(.+?))?\))?$/g;
|
|
@@ -7,7 +7,7 @@ import { ChronologyReplacerActions } from "../ChronologyReplacerActions.js";
|
|
|
7
7
|
import { TimeTextBuilder } from "../../text/TimeTextBuilder.js";
|
|
8
8
|
import { RR0CaseSummaryMapper } from "./RR0CaseSummaryMapper";
|
|
9
9
|
import { RR0FileDatasource } from "./RR0FileDatasource";
|
|
10
|
-
import {
|
|
10
|
+
import { CMSContext } from "../../../CMSContext";
|
|
11
11
|
export declare class RR0TestDatasource extends RR0Datasource implements Datasource<RR0CaseSummary> {
|
|
12
12
|
timeTextBuilder: TimeTextBuilder;
|
|
13
13
|
constructor();
|
|
@@ -19,5 +19,5 @@ export declare class RR0TestMapping implements RR0CaseMapping<RR0CaseSummary> {
|
|
|
19
19
|
backupDatasource: RR0FileDatasource;
|
|
20
20
|
mapper: RR0CaseSummaryMapper;
|
|
21
21
|
constructor(actions: ChronologyReplacerActions);
|
|
22
|
-
init(build:
|
|
22
|
+
init(build: CMSContext): this;
|
|
23
23
|
}
|
|
@@ -25,11 +25,13 @@ export class RR0TestMapping {
|
|
|
25
25
|
}
|
|
26
26
|
init(build) {
|
|
27
27
|
this.mapper = new RR0CaseSummaryMapper(new URL("https://rr0.org"), "time", ["Beau, Jérôme"]);
|
|
28
|
-
this.backupDatasource = new RR0FileDatasource
|
|
28
|
+
this.backupDatasource = new class extends RR0FileDatasource {
|
|
29
|
+
}(this.mapper);
|
|
29
30
|
return this;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
describe("RR0CaseSource", () => {
|
|
34
|
+
const rr0TestMapping = new RR0TestMapping({ read: ["fetch"], write: [] });
|
|
33
35
|
const testCase = new class extends DatasourceTestCase {
|
|
34
36
|
constructor(mapping, sourceCases) {
|
|
35
37
|
super(mapping, sourceCases);
|
|
@@ -72,7 +74,7 @@ describe("RR0CaseSource", () => {
|
|
|
72
74
|
return " " + HtmlTag.toString("span", authorStr + sourceItems.join(", "), { class: "source" });
|
|
73
75
|
}).join("");
|
|
74
76
|
}
|
|
75
|
-
}(
|
|
77
|
+
}(rr0TestMapping.init(rr0TestUtil), rr0TestCases);
|
|
76
78
|
let context;
|
|
77
79
|
beforeEach(() => {
|
|
78
80
|
context = rr0TestUtil.time.newHtmlContext("1/9/7/0/03/index.html");
|
|
@@ -9,6 +9,6 @@ export declare class RR0FileDatasource extends RR0Datasource implements Datasour
|
|
|
9
9
|
protected mapper: CaseMapper<RR0Context, RR0CaseSummary, RR0CaseSummary>;
|
|
10
10
|
protected readonly file: CsvFileSource<unknown>;
|
|
11
11
|
constructor(mapper: CaseMapper<RR0Context, RR0CaseSummary, RR0CaseSummary>);
|
|
12
|
-
save(context: HtmlRR0Context, fetched: any[], fetchTime: Date):
|
|
12
|
+
save(context: HtmlRR0Context, fetched: any[], fetchTime: Date): string;
|
|
13
13
|
protected readCases(context: HtmlRR0Context): Promise<RR0CaseSummary[]>;
|
|
14
14
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, test } from "@javarome/testscript";
|
|
2
|
+
import { rr0TestUtil } from "../../../test/index.js";
|
|
3
|
+
import { rr0TestCases } from "./RR0TestCases.js";
|
|
4
|
+
import { RR0CaseSummaryMapper } from "./RR0CaseSummaryMapper";
|
|
5
|
+
import { RR0FileDatasource } from "./RR0FileDatasource";
|
|
6
|
+
describe("RR0FileDatasource", () => {
|
|
7
|
+
test("save", async () => {
|
|
8
|
+
const csvMapper = new RR0CaseSummaryMapper(new URL("https://rr0.org"), "time", ["Beau, Jérôme"]);
|
|
9
|
+
const datasource = new RR0FileDatasource(csvMapper);
|
|
10
|
+
const context = rr0TestUtil.newHtmlContext("time/1/9/7/0/03/index.html");
|
|
11
|
+
const fileName = datasource.save(context, rr0TestCases, new Date("2025-01-01"));
|
|
12
|
+
expect(fileName).toBe("test/time/1/9/7/0/03/BeauJerome_RR0.csv");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -4,7 +4,7 @@ import { RR0CaseMapping } from "./RR0CaseMapping.js";
|
|
|
4
4
|
import { RR0CaseSummary } from "./RR0CaseSummary.js";
|
|
5
5
|
import { RR0FileDatasource } from "./RR0FileDatasource.js";
|
|
6
6
|
import { RR0Datasource } from "./RR0Datasource";
|
|
7
|
-
import {
|
|
7
|
+
import { CMSContext } from "../../../CMSContext";
|
|
8
8
|
export declare class RR0Mapping implements RR0CaseMapping<RR0CaseSummary> {
|
|
9
9
|
readonly actions: ChronologyReplacerActions;
|
|
10
10
|
static baseUrl: URL;
|
|
@@ -13,5 +13,5 @@ export declare class RR0Mapping implements RR0CaseMapping<RR0CaseSummary> {
|
|
|
13
13
|
backupDatasource: RR0FileDatasource;
|
|
14
14
|
mapper: RR0CaseSummaryMapper;
|
|
15
15
|
constructor(actions: ChronologyReplacerActions);
|
|
16
|
-
init(build:
|
|
16
|
+
init(build: CMSContext): this;
|
|
17
17
|
}
|