Skip to content

Commit

Permalink
Add USFM serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
ddaspit committed Nov 4, 2024
1 parent 17f075d commit ab7c103
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 107 deletions.
9 changes: 0 additions & 9 deletions packages/core/src/diagnostic/diagnostic-provider.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { Observable } from 'rxjs';

import { Document } from '../document/document';
import { DocumentManager } from '../document/document-manager';
import { Diagnostic } from './diagnostic';
import { DiagnosticFix } from './diagnostic-fix';

export type DiagnosticProviderFactory<T extends Document = Document> = (
DocumentManager: DocumentManager<T>,
) => DiagnosticProvider;
export type DiagnosticProviderConstructor<T extends Document = Document> = new (
documentManager: DocumentManager<T>,
) => DiagnosticProvider;

export interface DiagnosticsChanged {
uri: string;
version?: number;
Expand Down
7 changes: 1 addition & 6 deletions packages/core/src/diagnostic/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
export type { Diagnostic } from './diagnostic';
export { DiagnosticSeverity } from './diagnostic';
export type { DiagnosticFix } from './diagnostic-fix';
export type {
DiagnosticProvider,
DiagnosticProviderConstructor,
DiagnosticProviderFactory,
DiagnosticsChanged,
} from './diagnostic-provider';
export type { DiagnosticProvider, DiagnosticsChanged } from './diagnostic-provider';
2 changes: 1 addition & 1 deletion packages/core/src/document/document-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,6 @@ class TestEnvironment {
return { uri: document.uri, format: 'plaintext', version, content: changes[0].text };
});

this.docManager = new DocumentManager(this.docReader, this.docFactory);
this.docManager = new DocumentManager(this.docFactory, this.docReader);
}
}
2 changes: 1 addition & 1 deletion packages/core/src/document/document-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export class DocumentManager<T extends Document> {
private readonly changedSubject = new Subject<DocumentChanged<T>>();

constructor(
private readonly reader: DocumentReader | undefined,
private readonly factory: DocumentFactory<T>,
private readonly reader?: DocumentReader,
) {}

get created$(): Observable<DocumentCreated<T>> {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/document/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export { ScriptureOptBreak } from './scripture-optbreak';
export { ScriptureParagraph } from './scripture-paragraph';
export { ScriptureRef } from './scripture-ref';
export { ScriptureRow } from './scripture-row';
export type { ScriptureSerializer } from './scripture-serializer';
export { ScriptureSidebar } from './scripture-sidebar';
export { ScriptureTable } from './scripture-table';
export { ScriptureText } from './scripture-text';
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/document/scripture-book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ScriptureContainer } from './scripture-container';
import { ScriptureNodeType } from './scripture-node';

export class ScriptureBook extends ScriptureContainer {
public readonly style = 'id';

constructor(public readonly code: string) {
super();
}
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/document/scripture-chapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export class ScriptureChapter extends ScriptureMilestone {
public readonly number: string,
public readonly altNumber?: string,
public readonly pubNumber?: string,
public readonly sid?: string,
public readonly eid?: string,
sid?: string,
eid?: string,
range?: Range,
) {
super('c', sid, eid, undefined, range);
super('c', true, sid, eid, undefined, range);
}

get type(): ScriptureNodeType {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/document/scripture-milestone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ScriptureNodeType } from './scripture-node';
export class ScriptureMilestone extends ScriptureLeaf {
constructor(
public readonly style: string,
public readonly isStart: boolean,
public readonly sid?: string,
public readonly eid?: string,
public readonly attributes: Record<string, string> = {},
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/document/scripture-serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ScriptureNode } from './scripture-node';

export interface ScriptureSerializer {
serialize(nodes: ScriptureNode[] | ScriptureNode): string;
}
3 changes: 2 additions & 1 deletion packages/core/src/document/scripture-sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { ScriptureContainer } from './scripture-container';
import { ScriptureNodeType } from './scripture-node';

export class ScriptureSidebar extends ScriptureContainer {
public readonly style = 'esb';

constructor(
public readonly style: string,
public readonly category?: string,
children?: ScriptureContainer[],
) {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/document/scripture-verse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export class ScriptureVerse extends ScriptureMilestone {
public readonly number: string,
public readonly altNumber?: string,
public readonly pubNumber?: string,
public readonly sid?: string,
public readonly eid?: string,
sid?: string,
eid?: string,
range?: Range,
) {
super('v', sid, eid, undefined, range);
super('v', true, sid, eid, undefined, range);
}

get type(): ScriptureNodeType {
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/formatting/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
export type {
OnTypeFormattingProvider,
OnTypeFormattingProviderConstructor,
OnTypeFormattingProviderFactory,
} from './on-type-formatting-provider';
export type { OnTypeFormattingProvider } from './on-type-formatting-provider';
9 changes: 0 additions & 9 deletions packages/core/src/formatting/on-type-formatting-provider.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import { Position } from '../common/position';
import { TextEdit } from '../common/text-edit';
import { Document } from '../document/document';
import { DocumentManager } from '../document/document-manager';

export type OnTypeFormattingProviderFactory<T extends Document = Document> = (
DocumentManager: DocumentManager<T>,
) => OnTypeFormattingProvider;
export type OnTypeFormattingProviderConstructor<T extends Document = Document> = new (
documentManager: DocumentManager<T>,
) => OnTypeFormattingProvider;

export interface OnTypeFormattingProvider {
readonly id: string;
Expand Down
53 changes: 9 additions & 44 deletions packages/core/src/workspace/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,23 @@ import { Position } from '../common/position';
import { TextEdit } from '../common/text-edit';
import { Diagnostic } from '../diagnostic/diagnostic';
import { DiagnosticFix } from '../diagnostic/diagnostic-fix';
import {
DiagnosticProvider,
DiagnosticProviderConstructor,
DiagnosticProviderFactory,
DiagnosticsChanged,
} from '../diagnostic/diagnostic-provider';
import { Document } from '../document/document';
import { DocumentFactory } from '../document/document-factory';
import { DocumentManager } from '../document/document-manager';
import { DocumentReader } from '../document/document-reader';
import {
OnTypeFormattingProvider,
OnTypeFormattingProviderConstructor,
OnTypeFormattingProviderFactory,
} from '../formatting/on-type-formatting-provider';
import { DiagnosticProvider, DiagnosticsChanged } from '../diagnostic/diagnostic-provider';
import { OnTypeFormattingProvider } from '../formatting/on-type-formatting-provider';

export interface WorkspaceConfig<T extends Document = Document> {
documentReader?: DocumentReader;
documentFactory: DocumentFactory<T>;
diagnosticProviders?: (DiagnosticProviderFactory<T> | DiagnosticProviderConstructor<T>)[];
onTypeFormattingProviders?: (OnTypeFormattingProviderFactory<T> | OnTypeFormattingProviderConstructor<T>)[];
export interface WorkspaceConfig {
diagnosticProviders?: DiagnosticProvider[];
onTypeFormattingProviders?: OnTypeFormattingProvider[];
}

export class Workspace<T extends Document = Document> {
export class Workspace {
private readonly diagnosticProviders: Map<string, DiagnosticProvider>;
private readonly onTypeFormattingProviders: Map<string, OnTypeFormattingProvider>;
private readonly lastDiagnosticChangedEvents = new Map<string, DiagnosticsChanged[]>();

public readonly documentManager: DocumentManager<T>;
public readonly diagnosticsChanged$: Observable<DiagnosticsChanged>;

constructor(config: WorkspaceConfig<T>) {
this.documentManager = new DocumentManager(config.documentReader, config.documentFactory);
this.diagnosticProviders = new Map(
config.diagnosticProviders?.map((factory) => {
let provider: DiagnosticProvider;
try {
provider = new (factory as DiagnosticProviderConstructor<T>)(this.documentManager);
} catch {
provider = (factory as DiagnosticProviderFactory<T>)(this.documentManager);
}
return [provider.id, provider];
}),
);
constructor(config: WorkspaceConfig) {
this.diagnosticProviders = new Map(config.diagnosticProviders?.map((provider) => [provider.id, provider]));
this.diagnosticsChanged$ = merge(
...Array.from(this.diagnosticProviders.values()).map((provider, i) =>
provider.diagnosticsChanged$.pipe(
Expand All @@ -58,15 +31,7 @@ export class Workspace<T extends Document = Document> {
),
).pipe(map((e) => this.getCombinedDiagnosticChangedEvent(e.uri, e.version)));
this.onTypeFormattingProviders = new Map(
config.onTypeFormattingProviders?.map((factory) => {
let provider: OnTypeFormattingProvider;
try {
provider = new (factory as OnTypeFormattingProviderConstructor<T>)(this.documentManager);
} catch {
provider = (factory as OnTypeFormattingProviderFactory<T>)(this.documentManager);
}
return [provider.id, provider];
}),
config.onTypeFormattingProviders?.map((provider) => [provider.id, provider]),
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/eslint-config/library.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default tseslint.config(
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
},
},
{
Expand Down
1 change: 1 addition & 0 deletions packages/usfm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { UsfmDocument } from './usfm-document';
export { UsfmDocumentFactory } from './usfm-document-factory';
export { UsfmScriptureSerializer } from './usfm-scripture-serializer';
7 changes: 4 additions & 3 deletions packages/usfm/src/usfm-document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ class UsfmDocumentBuilder extends UsfmParserHandlerBase {
this.endContainer(state, false);
}

startSidebar(state: UsfmParserState, marker: string, category: string | undefined): void {
this.startContainer(state, new ScriptureSidebar(marker, category));
startSidebar(state: UsfmParserState, _marker: string, category: string | undefined): void {
this.startContainer(state, new ScriptureSidebar(category));
}

endSidebar(state: UsfmParserState, _marker: string, closed: boolean): void {
Expand Down Expand Up @@ -279,12 +279,13 @@ class UsfmDocumentBuilder extends UsfmParserHandlerBase {
milestone(
state: UsfmParserState,
marker: string,
_startMilestone: boolean,
startMilestone: boolean,
attributes: readonly UsfmAttribute[] | undefined,
): void {
this.appendChild(
new ScriptureMilestone(
marker,
startMilestone,
undefined,
undefined,
UsfmDocumentBuilder.convertAttributes(attributes),
Expand Down
68 changes: 68 additions & 0 deletions packages/usfm/src/usfm-scripture-serializer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { UsfmStylesheet } from '@sillsdev/machine/corpora';
import { describe, expect, it } from 'vitest';

import { UsfmDocument } from './usfm-document';
import { UsfmScriptureSerializer } from './usfm-scripture-serializer';

describe('UsfmScriptureSerializer', () => {
it('single paragraph', () => {
const usfm = normalize(`\\p This is a paragraph.`);
const result = serialize(usfm);
expect(result).toEqual(usfm);
});

it('multiple paragraphs', () => {
const usfm = normalize(`\\p This is a paragraph.
\\p This is another paragraph.`);
const result = serialize(usfm);
expect(result).toEqual(usfm);
});

it('nested character style', () => {
const usfm = normalize(`\\add an addition containing the word \\+nd Lord\\+nd*\\add*`);
const result = serialize(usfm);
expect(result).toEqual(usfm);
});

it('attributes', () => {
const usfm = normalize(`\\fig At once they left their nets.|src="avnt016.jpg" size="span" ref="1.18"\\fig*`);
const result = serialize(usfm);
expect(result).toEqual(usfm);
});

it('default attribute', () => {
const usfm = normalize(`\\w gracious|grace\\w*`);
const result = serialize(usfm);
expect(result).toEqual(usfm);
});

it('chapter and verse', () => {
const usfm = normalize(`\\c 1
\\p
\\v 1 This is a verse.`);
const result = serialize(usfm);
expect(result).toEqual(usfm);
});

it('table', () => {
const usfm = normalize(`\\tr \\th1 Tribe \\th2 Leader \\thr3 Number
\\tr \\tc1 Reuben \\tc2 Elizur son of Shedeur \\tcr3 46,500
\\tr \\tc1 Simeon \\tc2 Shelumiel son of Zurishaddai \\tcr3 59,300
\\tr \\tc1 Gad \\tc2 Eliasaph son of Deuel \\tcr3 45,650
\\tr \\tcr1-2 Total: \\tcr3 151,450`);
const result = serialize(usfm);
expect(result).toEqual(usfm);
});
});

function serialize(usfm: string): string {
const stylesheet = new UsfmStylesheet('usfm.sty');
const document = new UsfmDocument('uri', 1, usfm, stylesheet);
const serializer = new UsfmScriptureSerializer(stylesheet);

return normalize(serializer.serialize(document));
}

function normalize(text: string): string {
return text.replace(/\r?\n/g, '\n').trim();
}
Loading

0 comments on commit ab7c103

Please sign in to comment.