Repository: FHIR/sushi
Pull request #222 (2020-02-14 19:07:13)

Support user provided IG menu.xml
This PR adds support for a user provided `menu.xml` file. The file should be put in `ig-data/input/includes`. As part of this, it also supports copying over any other files that are placed in that folder. The menu and any additional files will be placed into `outputDir/input/includes/`.


Patch:
diff --git a/src/ig/IGExporter.ts b/src/ig/IGExporter.ts
index 2447e52..59b7e74 100644
--- a/src/ig/IGExporter.ts
+++ b/src/ig/IGExporter.ts
@@ -47,6 +47,7 @@ export class IGExporter {
     this.addIndex(outPath);
     this.addOtherPageContent(outPath);
     this.addImages(outPath);
+    this.addIncludeContents(outPath);
     this.addIgIni(outPath);
     this.addPackageList(outPath);
     this.addImplementationGuide(outPath);
@@ -180,7 +181,7 @@ export class IGExporter {
    *
    * @param igPath {string} - the path where the IG is exported to
    */
-  private addIndex(igPath: string) {
+  private addIndex(igPath: string): void {
     ensureDirSync(path.join(igPath, 'input', 'pagecontent'));
 
     // If the user provided an index.md file, use that
@@ -217,7 +218,7 @@ export class IGExporter {
    *
    * @param igPath {string} - the path where the IG is exported to
    */
-  private addOtherPageContent(igPath: string) {
+  private addOtherPageContent(igPath: string): void {
     const inputPageContentPath = path.join(this.igDataPath, 'input', 'pagecontent');
     if (fs.existsSync(inputPageContentPath)) {
       const pages = fs
@@ -264,7 +265,7 @@ export class IGExporter {
    *
    * @param igPath {string} - the path where the IG is exported to
    */
-  private addImages(igPath: string) {
+  private addImages(igPath: string): void {
     // If the user provided additional image files, include them
     const inputImagesPath = path.join(this.igDataPath, 'input', 'images');
     if (fs.existsSync(inputImagesPath)) {
@@ -272,10 +273,23 @@ export class IGExporter {
     }
   }
 
+  /**
+   * Adds any user provided includes files
+   * A user provided menu.xml will be in this folder. If one is not provided, the static one SUSHI provides will be used.
+   *
+   * @param {string} igPath - the path where the IG is exported to
+   */
+  private addIncludeContents(igPath: string): void {
+    const includesPath = path.join(this.igDataPath, 'input', 'includes');
+    if (fs.existsSync(includesPath)) {
+      fs.copySync(includesPath, path.join(igPath, 'input', 'includes'));
+    }
+  }
+
   /**
    * Add each of the resources from the package to the ImplementationGuide JSON file.
    */
-  private addResources() {
+  private addResources(): void {
     const resources: (StructureDefinition | ValueSet | CodeSystem)[] = [
       ...sortBy(this.pkg.profiles, sd => sd.name),
       ...sortBy(this.pkg.extensions, sd => sd.name),


Test patch:
diff --git a/test/ig/IGExporter.test.ts b/test/ig/IGExporter.test.ts
index dc450d9..caab270 100644
--- a/test/ig/IGExporter.test.ts
+++ b/test/ig/IGExporter.test.ts
@@ -267,6 +267,14 @@ describe('IGExporter', () => {
       const content = fs.readFileSync(indexPath, 'utf8');
       expect(content).toMatch('Provides a simple example of how FSH can be used to create an IG');
     });
+
+    it('should generate a default menu.xml', () => {
+      const menuPath = path.join(tempOut, 'input', 'includes', 'menu.xml');
+      expect(fs.existsSync(menuPath)).toBeTruthy();
+      const content = fs.readFileSync(menuPath, 'utf8');
+      expect(content).toMatch('<li><a href="index.html">IG Home</a></li>');
+      expect(content).toMatch('<li><a href="toc.html">Table of Contents</a></li>');
+    });
   });
 
   describe('#customized-ig', () => {
@@ -364,6 +372,21 @@ describe('IGExporter', () => {
       });
     });
 
+    it('should use the user-provided menu.xml if it exists', () => {
+      const menuPath = path.join(tempOut, 'input', 'includes', 'menu.xml');
+      expect(fs.existsSync(menuPath)).toBeTruthy();
+      const content = fs.readFileSync(menuPath, 'utf8');
+      expect(content).toMatch('<li><a href="index.html">My special menu</a></li>');
+      expect(content).toMatch('<li><a href="toc.html">Customized Table of Contents</a></li>');
+    });
+
+    it('should include any additional user-provided files in includes', () => {
+      const otherIncludeFilePath = path.join(tempOut, 'input', 'includes', 'other.xml');
+      expect(fs.existsSync(otherIncludeFilePath)).toBeTruthy();
+      const content = fs.readFileSync(otherIncludeFilePath, 'utf8');
+      expect(content).toMatch('<li><a href="index.html">Some other non-menu file</a></li>');
+    });
+
     it('should include additional user-provided pages of valid file type', () => {
       const pageContentPath = path.join(tempOut, 'input', 'pagecontent');
       expect(fs.existsSync(pageContentPath)).toBeTruthy();
diff --git a/test/ig/fixtures/customized-ig/ig-data/input/includes/menu.xml b/test/ig/fixtures/customized-ig/ig-data/input/includes/menu.xml
new file mode 100644
index 0000000..c6ec2df
--- /dev/null
+++ b/test/ig/fixtures/customized-ig/ig-data/input/includes/menu.xml
@@ -0,0 +1,4 @@
+<ul xmlns="http://www.w3.org/1999/xhtml" class="nav navbar-nav">
+  <li><a href="index.html">My special menu</a></li>
+  <li><a href="toc.html">Customized Table of Contents</a></li>
+</ul>
\ No newline at end of file
diff --git a/test/ig/fixtures/customized-ig/ig-data/input/includes/other.xml b/test/ig/fixtures/customized-ig/ig-data/input/includes/other.xml
new file mode 100644
index 0000000..433f461
--- /dev/null
+++ b/test/ig/fixtures/customized-ig/ig-data/input/includes/other.xml
@@ -0,0 +1,3 @@
+<ul xmlns="http://www.w3.org/1999/xhtml" class="nav navbar-nav">
+  <li><a href="index.html">Some other non-menu file</a></li>
+</ul>
\ No newline at end of file