Skip to content

Commit

Permalink
Create index files for FSH-defined entities
Browse files Browse the repository at this point in the history
There is one entry in the index for each FSH definition that results in
an exported JSON file. Inline instances do not appear in their own file,
so they do not appear in the index. The index is created in two formats.
There is a txt file that is formatted for human readability, and there
is a JSON file that is useful for machine processing.
  • Loading branch information
mint-thompson committed Oct 25, 2023
1 parent 8ee9d29 commit 74b5ee9
Show file tree
Hide file tree
Showing 17 changed files with 618 additions and 106 deletions.
335 changes: 239 additions & 96 deletions npm-shrinkwrap.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@types/readline-sync": "^1.4.3",
"@types/sax": "^1.2.1",
"@types/temp": "^0.8.34",
"@types/text-table": "^0.2.3",
"@types/valid-url": "^1.0.3",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
Expand Down Expand Up @@ -101,6 +102,7 @@
"sanitize-filename": "^1.6.3",
"sax": "^1.2.4",
"temp": "^0.9.1",
"text-table": "^0.2.0",
"title-case": "^3.0.2",
"valid-url": "^1.0.9",
"winston": "^3.3.3",
Expand Down
6 changes: 4 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
getRandomPun,
setIgnoredWarnings,
getLocalSushiVersion,
checkSushiVersion
checkSushiVersion,
writeFSHIndex
} from './utils';

const FSH_VERSION = '3.0.0-ballot';
Expand Down Expand Up @@ -282,7 +283,8 @@ async function runBuild(input: string, program: OptionValues, helpText: string)

logger.info('Converting FSH to FHIR resources...');
const outPackage = exportFHIR(tank, defs);
writeFHIRResources(outDir, outPackage, defs, program.snapshot);
const skippedResources = writeFHIRResources(outDir, outPackage, defs, program.snapshot);
writeFSHIndex(outDir, outPackage, input, skippedResources);

if (program.preprocessed) {
logger.info('Writing preprocessed FSH...');
Expand Down
5 changes: 5 additions & 0 deletions src/export/CodeSystemExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ export class CodeSystemExporter {
cleanResource(codeSystem, (prop: string) => ['_sliceName', '_primitive'].includes(prop));
this.updateCount(codeSystem, fshDefinition);
this.pkg.codeSystems.push(codeSystem);
this.pkg.fshMap.set(codeSystem.getFileName(), {
...fshDefinition.sourceInfo,
fshName: fshDefinition.name,
fshType: 'CodeSystem'
});
return codeSystem;
}

Expand Down
7 changes: 7 additions & 0 deletions src/export/InstanceExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,13 @@ export class InstanceExporter implements Fishable {
this.checkForNamelessSlices(fshDefinition, instanceDef, instanceOfStructureDefinition);
cleanResource(instanceDef);
this.pkg.instances.push(instanceDef);
if (fshDefinition.usage !== 'Inline') {
this.pkg.fshMap.set(instanceDef.getFileName(), {
...fshDefinition.sourceInfo,
fshName: fshDefinition.name,
fshType: fshDefinition.constructorName
});
}

// Once all rules are set, we should ensure that we did not add a duplicate profile URL anywhere
// if any of the duplicates have child elements, such as extension, merge them
Expand Down
5 changes: 4 additions & 1 deletion src/export/Package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
findImposeProfiles
} from '../fhirtypes/common';
import { StructureDefinition, InstanceDefinition, ValueSet, CodeSystem } from '../fhirtypes';
import { Configuration } from '../fshtypes';
import { Configuration, SourceInfo } from '../fshtypes';
import { Fishable, Type, Metadata } from '../utils/Fishable';

export class Package implements Fishable {
Expand All @@ -16,6 +16,9 @@ export class Package implements Fishable {
public readonly valueSets: ValueSet[] = [];
public readonly codeSystems: CodeSystem[] = [];

public readonly fshMap: Map<string, SourceInfo & { fshName: string; fshType: string }> =
new Map();

constructor(public readonly config: Configuration) {}

fish(
Expand Down
5 changes: 5 additions & 0 deletions src/export/StructureDefinitionExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,11 @@ export class StructureDefinitionExporter implements Fishable {
} else {
this.pkg.profiles.push(structDef);
}
this.pkg.fshMap.set(structDef.getFileName(), {
...fshDefinition.sourceInfo,
fshName: fshDefinition.name,
fshType: fshDefinition.constructorName
});

this.preprocessStructureDefinition(fshDefinition, structDef.type === 'Extension');

Expand Down
5 changes: 5 additions & 0 deletions src/export/ValueSetExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ export class ValueSetExporter {

cleanResource(vs, (prop: string) => ['_sliceName', '_primitive'].includes(prop));
this.pkg.valueSets.push(vs);
this.pkg.fshMap.set(vs.getFileName(), {
...fshDefinition.sourceInfo,
fshName: fshDefinition.name,
fshType: 'ValueSet'
});
return vs;
}
}
39 changes: 39 additions & 0 deletions src/utils/Processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Configuration } from '../fshtypes';
import { axiosGet } from './axiosUtils';
import { ImplementationGuideDependsOn } from '../fhirtypes';
import { FHIRVersionName, getFHIRVersionInfo } from '../utils/FHIRVersionUtils';
import table from 'text-table';

const EXT_PKG_TO_FHIR_PKG_MAP: { [key: string]: string } = {
'hl7.fhir.extensions.r2': 'hl7.fhir.r2.core#1.0.2',
Expand Down Expand Up @@ -509,6 +510,7 @@ export function writeFHIRResources(
) {
logger.info('Exporting FHIR resources as JSON...');
let count = 0;
const skippedResources: string[] = [];
const predefinedResources = defs.allPredefinedResources();
const writeResources = (
resources: {
Expand Down Expand Up @@ -543,6 +545,7 @@ export function writeFHIRResources(
'If you do want the FSH definition to be ignored, please comment the definition out ' +
'to remove this error.'
);
skippedResources.push(resource.getFileName());
}
});
};
Expand All @@ -564,6 +567,42 @@ export function writeFHIRResources(
writeResources(outPackage.instances.filter(i => i._instanceMeta.usage !== 'Inline'));

logger.info(`Exported ${count} FHIR resources as JSON.`);
return skippedResources;
}

export function writeFSHIndex(
outDir: string,
outPackage: Package,
inputDir: string,
skippedResources: string[] = []
) {
const index: string[][] = [];
const otherIndex: any[] = [];
for (const [fileName, fshInfo] of outPackage.fshMap) {
if (!skippedResources.includes(fileName)) {
const relativeInput = path.relative(inputDir, fshInfo.file);
index.push([
fshInfo.fshName,
fshInfo.fshType,
relativeInput,
`${fshInfo.location.startLine} - ${fshInfo.location.endLine}`,
fileName
]);
otherIndex.push({
fshFile: relativeInput,
fshName: fshInfo.fshName,
fshType: fshInfo.fshType,
startLine: fshInfo.location.startLine,
endLine: fshInfo.location.endLine,
outputFile: fileName
});
}
}
// write txt with nice formatting
index.unshift(['Name', 'Type', 'File', 'Lines', 'Output']);
fs.outputFileSync(path.join(outDir, 'fsh-generated', 'fsh-index.txt'), table(index));
// write json for machine usage
fs.outputJsonSync(path.join(outDir, 'fsh-generated', 'fsh-index.json'), otherIndex);
}

export function writePreprocessedFSH(outDir: string, inDir: string, tank: FSHTank) {
Expand Down
23 changes: 22 additions & 1 deletion test/export/CodeSystemExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { minimalConfig } from '../utils/minimalConfig';
describe('CodeSystemExporter', () => {
let defs: FHIRDefinitions;
let doc: FSHDocument;
let pkg: Package;
let exporter: CodeSystemExporter;

beforeAll(() => {
Expand All @@ -22,7 +23,7 @@ describe('CodeSystemExporter', () => {
beforeEach(() => {
doc = new FSHDocument('fileName');
const input = new FSHTank([doc], minimalConfig);
const pkg = new Package(input.config);
pkg = new Package(input.config);
const fisher = new TestFisher(input, defs, pkg);
exporter = new CodeSystemExporter(input, pkg, fisher);
loggerSpy.reset();
Expand All @@ -48,6 +49,26 @@ describe('CodeSystemExporter', () => {
});
});

it('should add source info for the exported code system to the package', () => {
const codeSystem = new FshCodeSystem('MyCodeSystem')
.withFile('Codes.fsh')
.withLocation([2, 5, 29, 33]);
doc.codeSystems.set(codeSystem.name, codeSystem);
const exported = exporter.export().codeSystems;
expect(exported.length).toBe(1);
expect(pkg.fshMap.get('CodeSystem-MyCodeSystem.json')).toEqual({
file: 'Codes.fsh',
location: {
startLine: 2,
startColumn: 5,
endLine: 29,
endColumn: 33
},
fshName: 'MyCodeSystem',
fshType: 'CodeSystem'
});
});

it('should export a code system with additional metadata', () => {
const codeSystem = new FshCodeSystem('MyCodeSystem');
codeSystem.id = 'CodeSystem1';
Expand Down
24 changes: 23 additions & 1 deletion test/export/InstanceExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('InstanceExporter', () => {
let defs: FHIRDefinitions;
let doc: FSHDocument;
let tank: FSHTank;
let pkg: Package;
let sdExporter: StructureDefinitionExporter;
let csExporter: CodeSystemExporter;
let vsExporter: ValueSetExporter;
Expand All @@ -57,7 +58,7 @@ describe('InstanceExporter', () => {
loggerSpy.reset();
doc = new FSHDocument('fileName');
tank = new FSHTank([doc], minimalConfig);
const pkg = new Package(tank.config);
pkg = new Package(tank.config);
const fisher = new TestFisher(tank, defs, pkg);
sdExporter = new StructureDefinitionExporter(tank, pkg, fisher);
csExporter = new CodeSystemExporter(tank, pkg, fisher);
Expand All @@ -84,6 +85,27 @@ describe('InstanceExporter', () => {
expect(exported.length).toBe(1);
});

it('should add source info for the exported instance to the package', () => {
const instance = new Instance('MyInstance')
.withFile('Examples.fsh')
.withLocation([25, 3, 52, 33]);
instance.instanceOf = 'Patient';
doc.instances.set(instance.name, instance);
const exported = exporter.export().instances;
expect(exported.length).toBe(1);
expect(pkg.fshMap.get('Patient-MyInstance.json')).toEqual({
file: 'Examples.fsh',
location: {
startLine: 25,
startColumn: 3,
endLine: 52,
endColumn: 33
},
fshName: 'MyInstance',
fshType: 'Instance'
});
});

it('should export multiple instances', () => {
const instanceFoo = new Instance('Foo');
instanceFoo.instanceOf = 'Patient';
Expand Down
23 changes: 22 additions & 1 deletion test/export/StructureDefinition.ExtensionExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ContainsRule, AssignmentRule, CaretValueRule } from '../../src/fshtypes
describe('ExtensionExporter', () => {
let defs: FHIRDefinitions;
let doc: FSHDocument;
let pkg: Package;
let exporter: StructureDefinitionExporter;

beforeAll(() => {
Expand All @@ -28,7 +29,7 @@ describe('ExtensionExporter', () => {
loggerSpy.reset();
doc = new FSHDocument('fileName');
const input = new FSHTank([doc], minimalConfig);
const pkg = new Package(input.config);
pkg = new Package(input.config);
const fisher = new TestFisher(input, defs, pkg);
exporter = new StructureDefinitionExporter(input, pkg, fisher);
});
Expand All @@ -45,6 +46,26 @@ describe('ExtensionExporter', () => {
expect(exported.length).toBe(1);
});

it('should add source info for the exported extension to the package', () => {
const extension = new Extension('Foo')
.withFile('GoodExtensions.fsh')
.withLocation([38, 4, 49, 15]);
doc.extensions.set(extension.name, extension);
const exported = exporter.export().extensions;
expect(exported.length).toBe(1);
expect(pkg.fshMap.get('StructureDefinition-Foo.json')).toEqual({
file: 'GoodExtensions.fsh',
location: {
startLine: 38,
startColumn: 4,
endLine: 49,
endColumn: 15
},
fshName: 'Foo',
fshType: 'Extension'
});
});

it('should export multiple extensions', () => {
const extensionFoo = new Extension('Foo');
const extensionBar = new Extension('Bar');
Expand Down
23 changes: 22 additions & 1 deletion test/export/StructureDefinition.LogicalExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { readFileSync } from 'fs-extra';
describe('LogicalExporter', () => {
let defs: FHIRDefinitions;
let doc: FSHDocument;
let pkg: Package;
let exporter: StructureDefinitionExporter;

beforeAll(() => {
Expand All @@ -34,7 +35,7 @@ describe('LogicalExporter', () => {
loggerSpy.reset();
doc = new FSHDocument('fileName');
const input = new FSHTank([doc], minimalConfig);
const pkg = new Package(input.config);
pkg = new Package(input.config);
const fisher = new TestFisher(input, defs, pkg);
exporter = new StructureDefinitionExporter(input, pkg, fisher);
});
Expand All @@ -51,6 +52,26 @@ describe('LogicalExporter', () => {
expect(exported.length).toBe(1);
});

it('should add source info for the exported logical model to the package', () => {
const logical = new Logical('Toasty')
.withFile('AlwaysLogical.fsh')
.withLocation([48, 4, 59, 15]);
doc.logicals.set(logical.name, logical);
const exported = exporter.export().logicals;
expect(exported.length).toBe(1);
expect(pkg.fshMap.get('StructureDefinition-Toasty.json')).toEqual({
file: 'AlwaysLogical.fsh',
location: {
startLine: 48,
startColumn: 4,
endLine: 59,
endColumn: 15
},
fshName: 'Toasty',
fshType: 'Logical'
});
});

it('should export multiple logical models', () => {
const logicalFoo = new Logical('Foo');
const logicalBar = new Logical('Bar');
Expand Down
22 changes: 21 additions & 1 deletion test/export/StructureDefinition.ProfileExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MismatchedTypeError } from '../../src/errors';
describe('ProfileExporter', () => {
let defs: FHIRDefinitions;
let doc: FSHDocument;
let pkg: Package;
let exporter: StructureDefinitionExporter;

beforeAll(() => {
Expand All @@ -29,7 +30,7 @@ describe('ProfileExporter', () => {
loggerSpy.reset();
doc = new FSHDocument('fileName');
const input = new FSHTank([doc], minimalConfig);
const pkg = new Package(input.config);
pkg = new Package(input.config);
const fisher = new TestFisher(input, defs, pkg);
exporter = new StructureDefinitionExporter(input, pkg, fisher);
});
Expand All @@ -47,6 +48,25 @@ describe('ProfileExporter', () => {
expect(exported.length).toBe(1);
});

it('should add source info for the exported profile to the package', () => {
const profile = new Profile('Foo').withFile('SomeProfiles.fsh').withLocation([28, 4, 39, 15]);
profile.parent = 'Basic';
doc.profiles.set(profile.name, profile);
const exported = exporter.export().profiles;
expect(exported.length).toBe(1);
expect(pkg.fshMap.get('StructureDefinition-Foo.json')).toEqual({
file: 'SomeProfiles.fsh',
location: {
startLine: 28,
startColumn: 4,
endLine: 39,
endColumn: 15
},
fshName: 'Foo',
fshType: 'Profile'
});
});

it('should export multiple profiles', () => {
const profileFoo = new Profile('Foo');
profileFoo.parent = 'Basic';
Expand Down
Loading

0 comments on commit 74b5ee9

Please sign in to comment.