Repository: FHIR/sushi (ts)

Issue:
Need to preserve ignoreWarnings.txt if supplied
Running SUSHI doesn't transfer the contents to build

Pull request description:
Ig data copy tweaks
Fixes #267 and #268.  See individual commits for details.

Patch:
diff --git a/src/ig/IGExporter.ts b/src/ig/IGExporter.ts
index 3f00c92..758ac3d 100644
--- a/src/ig/IGExporter.ts
+++ b/src/ig/IGExporter.ts
@@ -52,6 +52,7 @@ export class IGExporter {
     this.addIncludeContents(outPath);
     this.addIgIni(outPath);
     this.addPackageList(outPath);
+    this.addIgnoreWarningsFile(outPath);
     this.addImplementationGuide(outPath);
   }
 
@@ -367,6 +368,18 @@ export class IGExporter {
     }
   }
 
+  /**
+   * Adds user provided ignoreWarnings.txt file if it exists; otherwise the static one SUSHI provides will be used.
+   *
+   * @param {string} igPath - the path where the IG is exported to
+   */
+  private addIgnoreWarningsFile(igPath: string): void {
+    const ignorePath = path.join(this.igDataPath, 'input', 'ignoreWarnings.txt');
+    if (fs.existsSync(ignorePath)) {
+      fs.copyFileSync(ignorePath, path.join(igPath, 'input', 'ignoreWarnings.txt'));
+    }
+  }
+
   /**
    * Add each of the resources from the package to the ImplementationGuide JSON file.
    */
@@ -540,7 +553,14 @@ export class IGExporter {
     let merged = false;
     if (fs.existsSync(inputIniPath)) {
       merged = true;
-      const inputIni = ini.parse(fs.readFileSync(inputIniPath, 'utf8'));
+      let inputIniContents = fs.readFileSync(inputIniPath, 'utf8');
+      // FHIR allows templates to have versions identified using #.  E.g.,
+      //   template = hl7.fhir.template#0.1.0
+      // The ini library, however, treats # as a comment unless it is escaped.  So if it exists, we need to escape it.
+      inputIniContents = inputIniContents.replace(/^\s*template\s*=\s*[^#]+(#.+)?$/m, ($0, $1) =>
+        $1 ? $0.replace($1, `\\${$1}`) : $0
+      );
+      const inputIni = ini.parse(inputIniContents);
       if (Object.keys(inputIni).length > 1 || inputIni.IG == null) {
         logger.error('igi.ini file must contain an [IG] section with no other sections', {
           file: inputIniPath
@@ -574,12 +594,15 @@ export class IGExporter {
     const releaseParam = this.ig.definition.parameter.find(p => p.code === 'releaselabel');
     releaseParam.value = iniObj.ballotstatus;
 
-    // Finally, write it to disk
-    outputFileSync(
-      path.join(igPath, 'ig.ini'),
-      ini.encode(iniObj, { section: 'IG', whitespace: true })
+    // Now we need to do the reverse of what we did before.  If `#` is escaped, then unescape it.
+    let outputIniContents = ini.encode(iniObj, { section: 'IG', whitespace: true });
+    outputIniContents = outputIniContents.replace(/^template\s*=\s*.+?(\\#.+)?$/m, ($0, $1) =>
+      $1 ? $0.replace($1, $1.slice(1)) : $0
     );
 
+    // Finally, write it to disk
+    outputFileSync(path.join(igPath, 'ig.ini'), outputIniContents);
+
     if (merged) {
       logger.info('Merged ig-data/ig.ini w/ generated ig.ini');
     } else {


Test patch:
diff --git a/test/ig/IGExporter.test.ts b/test/ig/IGExporter.test.ts
index 70d7a0d..232b928 100644
--- a/test/ig/IGExporter.test.ts
+++ b/test/ig/IGExporter.test.ts
@@ -1,7 +1,7 @@
 import fs from 'fs-extra';
 import path from 'path';
 import temp from 'temp';
-import ini from 'ini';
+import os from 'os';
 import { IGExporter } from '../../src/ig';
 import { StructureDefinition, InstanceDefinition, CodeSystem } from '../../src/fhirtypes';
 import { Package } from '../../src/export';
@@ -86,16 +86,22 @@ describe('IGExporter', () => {
     it('should generate an ig.ini with the correct values based on the package.json', () => {
       const iniPath = path.join(tempOut, 'ig.ini');
       expect(fs.existsSync(iniPath)).toBeTruthy();
-      const content = ini.parse(fs.readFileSync(iniPath, 'utf8'));
-      expect(Object.keys(content.IG)).toHaveLength(8);
-      expect(content.IG.ig).toEqual('input/ImplementationGuide-sushi-test.json');
-      expect(content.IG.template).toEqual('fhir.base.template');
-      expect(content.IG['usage-stats-opt-out']).toBeFalsy();
-      expect(content.IG.copyrightyear).toEqual(`${new Date().getFullYear()}+`);
-      expect(content.IG.license).toEqual('CC0-1.0');
-      expect(content.IG.version).toEqual('0.1.0');
-      expect(content.IG.ballotstatus).toEqual('CI Build');
-      expect(content.IG.fhirspec).toEqual('http://build.fhir.org/');
+      const content = fs.readFileSync(iniPath, 'utf8');
+      expect(content).toEqual(
+        [
+          '[IG]',
+          'ig = input/ImplementationGuide-sushi-test.json',
+          'template = fhir.base.template',
+          'usage-stats-opt-out = false',
+          `copyrightyear = ${new Date().getFullYear()}+`,
+          'license = CC0-1.0',
+          'version = 0.1.0',
+          'ballotstatus = CI Build',
+          'fhirspec = http://build.fhir.org/'
+        ]
+          .map(ln => ln + os.EOL) // Windows: /r/n; Mac: /n
+          .join('')
+      );
     });
 
     it('should generate an ImplementationGuide resource based on the package', () => {
@@ -305,20 +311,26 @@ describe('IGExporter', () => {
     it('should generate an ig.ini with user-specified values overridden', () => {
       const iniPath = path.join(tempOut, 'ig.ini');
       expect(fs.existsSync(iniPath)).toBeTruthy();
-      const content = ini.parse(fs.readFileSync(iniPath, 'utf8'));
-      expect(Object.keys(content.IG)).toHaveLength(12);
-      expect(content.IG.ig).toEqual('input/ImplementationGuide-sushi-test.json');
-      expect(content.IG.template).toEqual('hl7.fhir.template');
-      expect(content.IG['usage-stats-opt-out']).toBeTruthy();
-      expect(content.IG.copyrightyear).toEqual('2018+');
-      expect(content.IG.license).toEqual('CC0-1.0');
-      expect(content.IG.version).toEqual('0.1.0');
-      expect(content.IG.ballotstatus).toEqual('STU1');
-      expect(content.IG.fhirspec).toEqual('http://hl7.org/fhir/R4/');
-      expect(content.IG.excludexml).toEqual('Yes');
-      expect(content.IG.excludejson).toEqual('Yes');
-      expect(content.IG.excludettl).toEqual('Yes');
-      expect(content.IG.excludeMaps).toEqual('Yes');
+      const content = fs.readFileSync(iniPath, 'utf8');
+      expect(content).toEqual(
+        [
+          '[IG]',
+          'ig = input/ImplementationGuide-sushi-test.json',
+          'template = hl7.fhir.template#0.1.0',
+          'usage-stats-opt-out = true',
+          'copyrightyear = 2018+',
+          'license = CC0-1.0',
+          'version = 0.1.0',
+          'ballotstatus = STU1',
+          'fhirspec = http://hl7.org/fhir/R4/',
+          'excludexml = Yes',
+          'excludejson = Yes',
+          'excludettl = Yes',
+          'excludeMaps = Yes'
+        ]
+          .map(ln => ln + os.EOL) // Windows: /r/n; Mac: /n
+          .join('')
+      );
     });
 
     it('should use the user-provided package-list.json when supplied', () => {
@@ -399,6 +411,18 @@ describe('IGExporter', () => {
       expect(content).toMatch('<li><a href="toc.html">Customized Table of Contents</a></li>');
     });
 
+    it('should use the user-provided ignoreWarnings.txt if it exists', () => {
+      const ignorePath = path.join(tempOut, 'input', 'ignoreWarnings.txt');
+      expect(fs.existsSync(ignorePath)).toBeTruthy();
+      const content = fs.readFileSync(ignorePath, 'utf8');
+      expect(content).toMatch(
+        'Code System URI "http://ncimeta.nci.nih.gov" is unknown so the code cannot be validated'
+      );
+      expect(content).toMatch(
+        'Code System URI "http://cancerstaging.org" is unknown so the code cannot be validated'
+      );
+    });
+
     it('should include any additional user-provided files in includes', () => {
       const otherIncludeFilePath = path.join(tempOut, 'input', 'includes', 'other.xml');
       expect(fs.existsSync(otherIncludeFilePath)).toBeTruthy();
@@ -713,20 +737,26 @@ describe('IGExporter', () => {
       // And ensure that invalid inputs did not override existing values
       const iniPath = path.join(tempOut, 'ig.ini');
       expect(fs.existsSync(iniPath)).toBeTruthy();
-      const content = ini.parse(fs.readFileSync(iniPath, 'utf8'));
-      expect(Object.keys(content.IG)).toHaveLength(12);
-      expect(content.IG.ig).toEqual('input/ImplementationGuide-sushi-test.json');
-      expect(content.IG.template).toEqual('hl7.fhir.template');
-      expect(content.IG['usage-stats-opt-out']).toBeTruthy();
-      expect(content.IG.copyrightyear).toEqual('2018+');
-      expect(content.IG.license).toEqual('CC0-1.0');
-      expect(content.IG.version).toEqual('0.1.0');
-      expect(content.IG.ballotstatus).toEqual('STU1');
-      expect(content.IG.fhirspec).toEqual('http://hl7.org/fhir/R4/');
-      expect(content.IG.excludexml).toEqual('Yes');
-      expect(content.IG.excludejson).toEqual('Yes');
-      expect(content.IG.excludettl).toEqual('Yes');
-      expect(content.IG.excludeMaps).toEqual('Yes');
+      const content = fs.readFileSync(iniPath, 'utf8');
+      expect(content).toEqual(
+        [
+          '[IG]',
+          'ig = input/ImplementationGuide-sushi-test.json',
+          'template = hl7.fhir.template',
+          'usage-stats-opt-out = true',
+          'copyrightyear = 2018+',
+          'license = CC0-1.0',
+          'version = 0.1.0',
+          'ballotstatus = STU1',
+          'fhirspec = http://hl7.org/fhir/R4/',
+          'excludexml = Yes',
+          'excludejson = Yes',
+          'excludettl = Yes',
+          'excludeMaps = Yes'
+        ]
+          .map(ln => ln + os.EOL) // Windows: /r/n; Mac: /n
+          .join('')
+      );
     });
 
     it('should add pages of an invalid file type but log a warning', () => {
diff --git a/test/ig/fixtures/customized-ig/ig-data/ig.ini b/test/ig/fixtures/customized-ig/ig-data/ig.ini
index 00c4ddf..f522266 100644
--- a/test/ig/fixtures/customized-ig/ig-data/ig.ini
+++ b/test/ig/fixtures/customized-ig/ig-data/ig.ini
@@ -1,6 +1,6 @@
 [IG]
 ig = input/ImplementationGuide-sushi-test.json
-template = hl7.fhir.template
+template = hl7.fhir.template#0.1.0
 usage-stats-opt-out = true
 copyrightyear = 2018+
 license = CC0-1.0
diff --git a/test/ig/fixtures/customized-ig/ig-data/input/ignoreWarnings.txt b/test/ig/fixtures/customized-ig/ig-data/input/ignoreWarnings.txt
new file mode 100644
index 0000000..97cda63
--- /dev/null
+++ b/test/ig/fixtures/customized-ig/ig-data/input/ignoreWarnings.txt
@@ -0,0 +1,2 @@
+Code System URI "http://ncimeta.nci.nih.gov" is unknown so the code cannot be validated
+Code System URI "http://cancerstaging.org" is unknown so the code cannot be validated