@thegetty/quire-cli 1.0.0-rc.11 → 1.0.0-rc.12

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/CHANGELOG.md CHANGED
@@ -18,6 +18,16 @@ Changelog entries are classified using the following labels:
18
18
 
19
19
  - `Removed`: for deprecated features removed in this release
20
20
 
21
+ ## [1.0.0-rc.12]
22
+
23
+ ### Added
24
+ - Handling for single-page PDFs
25
+ - Paged and Prince plugins to map quire pages to PDF page sections
26
+
27
+ ### Changed
28
+
29
+ - `quire clean` also removes `.11ty-vite` in the project root
30
+
21
31
  ## [1.0.0-rc.11]
22
32
 
23
33
  ### Bumped
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thegetty/quire-cli",
3
3
  "description": "Quire command-line interface",
4
- "version": "1.0.0-rc.11",
4
+ "version": "1.0.0-rc.12",
5
5
  "author": "Getty Digital",
6
6
  "license": "SEE LICENSE IN https://github.com/thegetty/quire/blob/main/LICENSE",
7
7
  "bugs": {
@@ -45,6 +45,7 @@
45
45
  "#lib/*.js": "./src/lib/*.js"
46
46
  },
47
47
  "dependencies": {
48
+ "ajv": "^8.16.0",
48
49
  "boxen": "^7.0.1",
49
50
  "chalk": "^5.2.0",
50
51
  "commander": "^10.0.0",
@@ -58,6 +59,7 @@
58
59
  "i18next": "^22.4.9",
59
60
  "inquirer": "^9.1.4",
60
61
  "install-npm-version": "^1.0.8",
62
+ "js-yaml": "^4.1.0",
61
63
  "loglevel": "^1.8.1",
62
64
  "open": "^8.4.0",
63
65
  "ora": "^6.1.2",
@@ -1,10 +1,51 @@
1
- import Command from '#src/Command.js'
2
1
  import { paths, projectRoot } from '#lib/11ty/index.js'
2
+ import Command from '#src/Command.js'
3
3
  import fs from 'fs-extra'
4
4
  import libPdf from '#lib/pdf/index.js'
5
5
  import open from 'open'
6
6
  import path from 'node:path'
7
- // import testcwd from '#helpers/test-cwd.js'
7
+ import { validateUserConfig } from '../../../11ty/_plugins/globalData/validator.js'
8
+ import yaml from 'js-yaml'
9
+
10
+ /**
11
+ * @function loadConfig(path) - loads and validates quire config, returns an empty object if not found
12
+ *
13
+ * @param {path} string - file path to config file
14
+ *
15
+ * @todo refactor loading configs to a separate module,
16
+ * which uses the same validator(s) as the build proceess
17
+ *
18
+ * @return {Object|undefined} User configuration object or undefined
19
+ */
20
+ function loadConfig(confPath) {
21
+ if (!fs.existsSync(confPath)) {
22
+ return undefined
23
+ }
24
+
25
+ const data = fs.readFileSync(confPath)
26
+ let config = yaml.load(data)
27
+
28
+ // NB: Schemas may be project specific so the CLI must loads from project root rather than package root
29
+ const schemaPath = path.join(projectRoot,'_plugins','schemas','config.json')
30
+
31
+ // TODO: Check the project version string for whether we should check the schema
32
+ if (fs.existsSync(schemaPath)) {
33
+
34
+ const sch = fs.readFileSync(schemaPath)
35
+ const schema = JSON.parse(sch)
36
+
37
+ try {
38
+ config = validateUserConfig('config',config,{config: schema})
39
+ } catch (err) {
40
+ console.error(err)
41
+ process.exit(1)
42
+ }
43
+ }
44
+
45
+ return config
46
+ }
47
+
48
+ const quireConfig = loadConfig(path.join(projectRoot,'content','_data','config.yaml'))
8
49
 
9
50
  /**
10
51
  * Quire CLI `pdf` Command
@@ -33,6 +74,7 @@ export default class PDFCommand extends Command {
33
74
  }
34
75
 
35
76
  constructor() {
77
+ const config = loadConfig(path.join(projectRoot,'content','_data','config.yaml'))
36
78
  super(PDFCommand.definition)
37
79
  }
38
80
 
@@ -41,22 +83,28 @@ export default class PDFCommand extends Command {
41
83
  console.debug('[CLI] Command \'%s\' called with options %o', this.name(), options)
42
84
  }
43
85
 
44
- const input = path.join(projectRoot, paths.output, 'pdf.html')
86
+ const publicationInput = path.join(projectRoot, paths.output, 'pdf.html')
87
+ const coversInput = path.join(projectRoot, paths.output, 'pdf-covers.html')
45
88
 
46
- if (!fs.existsSync(input)) {
47
- console.error(`Unable to find PDF input at ${input}\nPlease first run the 'quire build' command.`)
48
- return
89
+ if (quireConfig===undefined) {
90
+ console.error(`[quire pdf]: ERROR Unable to find a configuration file at ${path.join(projectRoot,'content','_data','config.yaml')}\nIs the command being run in a quire project?`)
91
+ process.exit(1)
92
+ }
93
+
94
+ if (!fs.existsSync(publicationInput)) {
95
+ console.error(`[quire pdf]: ERROR Unable to find PDF input at ${publicationInput}\nPlease first run the 'quire build' command.`)
96
+ process.exit(1)
49
97
  }
50
98
 
51
- const output = path.join(projectRoot, `${options.lib}.pdf`)
99
+ const output = quireConfig.pdf !== undefined ? path.join(paths.output, quireConfig.pdf.outputDir, `${quireConfig.pdf.filename}.pdf`) : path.join(projectRoot, `${options.lib}.pdf`)
52
100
 
53
- const pdfLib = await libPdf(options.lib, { debug: options.debug })
54
- await pdfLib(input, output)
101
+ const pdfLib = await libPdf(options.lib, { ...options, pdfConfig: quireConfig.pdf })
102
+ await pdfLib(publicationInput, coversInput, output)
55
103
 
56
104
  try {
57
105
  if (fs.existsSync(output) && options.open) open(output)
58
106
  } catch (error) {
59
- console.error(error)
107
+ console.error(`[quire pdf]: ERROR`,error)
60
108
  process.exit(1)
61
109
  }
62
110
  }
@@ -9,6 +9,7 @@ import path from 'node:path'
9
9
  */
10
10
  export async function clean (projectRoot, paths, options = {}) {
11
11
  const pathsToClean = [
12
+ path.join(projectRoot, '.11ty-vite'),
12
13
  path.join(projectRoot, paths.epub),
13
14
  path.join(projectRoot, paths.output),
14
15
  path.join(projectRoot, '*.epub'),
@@ -1,12 +1,19 @@
1
1
  ## CLI lib/PDF Module
2
2
 
3
- This module implements a façade for PDF generation libries.
4
- The module exports a single method that accepts a `lib` option and delegates to a façade for the specified PDF library.
3
+ This module provides an abstraction to ease PDF generation across PrinceXML and Paged.js. The exported module dynamically loads a wrapper to align the libraries' JS APIs by exporting a single method that accepts a `lib` option and returns an async function that takes input, output, and option params.
4
+
5
+ The module also provides plugins for Prince and Paged.js to map quire webpages to PDF pages. In both cases this is achieved after PDF rendering by querying the HTML document that was printed for `.quire-page` elements and using the PDF generator's APIs to determine content page ids, page data like titles and contributors, and first / last pages. They then use a simple stripping algorithm with `pdf-lib` to split the pages for `--page-pdf` flagged runs.
5
6
 
6
7
  ### Paged.js Façade
7
8
 
8
- See the [`Paged.js` documentation](https://gitlab.coko.foundation/pagedjs/).
9
+ We use [`pagedjs-cli`](https://gitlab.coko.foundation/pagedjs/pagedjs-cli), which adds a headless to paged.js to facilitate serializing to PDF files.
10
+
11
+ For more details on the PDF generating API see the [`Paged.js` documentation](https://gitlab.coko.foundation/pagedjs/). See paged.js's [hooks documentation](https://pagedjs.org/documentation/10-handlers-hooks-and-custom-javascript/) for details on the `afterRendered` hook that quire uses to generate the PDF page map.
9
12
 
10
13
  ### Prince XML Façade
11
14
 
12
- See the [Prince Command-line Reference](https://www.princexml.com/doc/command-line/).
15
+ The Prince abstraction wraps the command line execution of the Prince executable.
16
+
17
+ See the [Prince Command-line Reference](https://www.princexml.com/doc/command-line/). Prince's [scripting documentation](https://www.princexml.com/doc/javascript/) has details on its runtime Javascript implementation, *which is only compatible up to ES5*.
18
+
19
+ The Prince plugin passes page map data as JSON to STDOUT.
@@ -17,14 +17,14 @@ export default async (name = 'pagedjs', options = {}) => {
17
17
  case 'paged':
18
18
  case 'pagedjs': {
19
19
  lib.name = 'Paged.js'
20
- lib.options = { debug: options.debug }
20
+ lib.options = options
21
21
  lib.path = path.join(__dirname, 'paged.js')
22
22
  break
23
23
  }
24
24
  case 'prince':
25
25
  case 'princexml': {
26
26
  lib.name = 'Prince'
27
- lib.options = { debug: options.debug, verbose: options.verbose }
27
+ lib.options = options
28
28
  lib.path = path.join(__dirname, 'prince.js')
29
29
  break
30
30
  }
@@ -35,8 +35,8 @@ export default async (name = 'pagedjs', options = {}) => {
35
35
 
36
36
  const { default: pdfLib } = await dynamicImport(lib.path)
37
37
 
38
- return async (input, output) => {
38
+ return async (publicationInput, coversInput, output) => {
39
39
  console.info(`[CLI:lib/pdf] generating PDF using ${lib.name}`)
40
- return await pdfLib(input, output, lib.options)
40
+ return await pdfLib(publicationInput, coversInput, output, lib.options)
41
41
  }
42
42
  }
@@ -1,19 +1,38 @@
1
1
  import Printer from 'pagedjs-cli'
2
+
2
3
  import fs from 'fs-extra'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ import { splitPdf } from './split.js'
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = path.dirname(__filename)
11
+
12
+ // FIXME: This module swallows errors currently.
3
13
 
4
14
  /**
5
- * A façade module for interacting with Paged.js
15
+ * A façade module for interacting with Paged.js and pagedjs-cli
6
16
  * @see https://gitlab.coko.foundation/pagedjs/
7
17
  */
8
- export default async (input, output, options = {}) => {
18
+ export default async (publicationInput, coversInput, output, options = {}) => {
9
19
  /**
10
20
  * Configure the Paged.js Printer options
11
21
  * @see https://gitlab.coko.foundation/pagedjs/pagedjs-cli/-/blob/main/src/cli.js
12
22
  */
23
+
24
+ let additionalScripts = []
25
+
26
+ const { pdfConfig } = options
27
+
28
+ additionalScripts.push( path.join(__dirname, 'pagedPlugin.js') )
29
+
13
30
  const printerOptions = {
14
31
  allowLocal: true,
15
32
  debug: options.debug || false,
16
33
  enableWarnings: options.debug || false,
34
+ closeAfter: false,
35
+ additionalScripts,
17
36
  }
18
37
 
19
38
  if (options.debug) {
@@ -21,12 +40,12 @@ export default async (input, output, options = {}) => {
21
40
  console.debug(`[CLI:lib/pdf/pagedjs] Printer options\n${optionsOutput}`)
22
41
  }
23
42
 
24
- const printer = new Printer(printerOptions)
43
+ let printer = new Printer(printerOptions)
25
44
 
26
- printer.on('page', (page) => {
45
+ printer.on('page', (page,pageElement,breakToken) => {
27
46
  if (page.position === 0) {
28
47
  console.info(`[CLI:lib/pdf/pagedjs] loaded`)
29
- }
48
+ }
30
49
  })
31
50
 
32
51
  printer.on('rendered', (msg) => {
@@ -54,18 +73,78 @@ export default async (input, output, options = {}) => {
54
73
  }
55
74
 
56
75
  try {
57
- console.info(`[CLI:lib/pdf/pagedjs] printing ${input}`)
76
+ console.info(`[CLI:lib/pdf/pagedjs] printing ${publicationInput}`)
58
77
 
59
- const file = await printer.pdf(input, pdfOptions)
78
+ const file = await printer.pdf(publicationInput, pdfOptions)
60
79
  .catch((error) => console.error(error))
61
80
 
62
- printer.close()
81
+ let pageMap
82
+
83
+ // Now it's printed, create the pageMap by running JS in the printer's context
84
+ let coversFile
85
+
86
+ console.info(`[CLI:lib/pdf/pagedjs] generating page map`)
87
+ const pages = await printer.browser.pages()
88
+
89
+ if (pages.length > 0) {
90
+ pageMap = await pages[pages.length - 1].evaluate(() => {
91
+ // Retrieves the pageMap from our plugin
92
+ return window.pageMap ?? {} // eslint-disable-line no-undef
93
+ })
94
+ }
95
+
96
+ if ( pdfConfig?.pagePDF?.coverPage===true && fs.existsSync(coversInput) ) {
97
+ console.info(`[CLI:lib/pdf/pagedjs] printing ${coversInput}`)
98
+
99
+ const coverPrinter = new Printer(printerOptions)
100
+
101
+ coversFile = await coverPrinter.pdf(coversInput, pdfOptions)
102
+ .catch((error) => console.error(error))
103
+
104
+ const coverPages = await coverPrinter.browser.pages()
105
+
106
+ if (coverPages.length > 0) {
107
+ const coversMap = await coverPages[coverPages.length - 1].evaluate(() => {
108
+ // Retrieves the pageMap from our plugin
109
+ return window.pageMap ?? {} // eslint-disable-line no-undef
110
+ })
111
+
112
+ Object.values(coversMap).forEach( cov => {
113
+ if (cov.id in pageMap) {
114
+ pageMap[cov.id].coverPage = cov.startPage
115
+ }
116
+ })
117
+
118
+ }
119
+
120
+ coverPrinter.close()
121
+ }
122
+
123
+ // Leave the printer open for debug logs
124
+ if (!options.debug) {
125
+ printer.close()
126
+ }
63
127
 
64
128
  if (file && output) {
129
+ console.info(`[CLI:lib/pdf/pagedjs] writing file(s)`)
130
+
131
+ const { dir } = path.parse(output)
132
+ if (!fs.existsSync(dir)) {
133
+ fs.mkdirsSync(dir)
134
+ }
135
+
65
136
  await fs.promises.writeFile(output, file)
66
137
  .catch((error) => console.error(error))
138
+
139
+ const files = await splitPdf(file,coversFile,pageMap,options.pdfConfig)
140
+
141
+ Object.entries(files).forEach( async ([filePath,pagePdf]) => {
142
+ await fs.promises.writeFile(filePath,pagePdf)
143
+ .catch((error) => console.error(error))
144
+ })
67
145
  }
146
+
68
147
  } catch (ERR_FILE_NOT_FOUND) {
69
- console.error(`[CLI:lib/pdf/pagedjs] file not found ${input}`)
148
+ console.error(`[CLI:lib/pdf/pagedjs] file not found ${publicationInput}`)
70
149
  }
71
150
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @class pageTableMapper
3
+ *
4
+ * Responds to [paged.js afterRender hook](https://pagedjs.org/documentation/10-handlers-hooks-and-custom-javascript/) to map the PDF's page table and store it in window.pageMap
5
+ *
6
+ */
7
+ class pageTableMapper extends Paged.Handler { // eslint-disable-line no-undef
8
+ constructor(chunker, polisher, caller) {
9
+ super(chunker, polisher, caller)
10
+ }
11
+
12
+ /**
13
+ * @function afterRendered - fires after all pages are laid out and PDF data is available
14
+ *
15
+ * @param {Array<Page>} pages - Pages rendered from the document
16
+ */
17
+ afterRendered(pages) {
18
+ let pageMap = {}
19
+ let webpageKey
20
+
21
+ // Iterate pages, build lookup by finding the website page on each PDF page
22
+ for (const p of pages) {
23
+ const quirePageElement = p.element.querySelector('.quire-page')
24
+
25
+ if (!quirePageElement) { continue }
26
+ if (quirePageElement.dataset.pagePdf !== 'true') { continue }
27
+
28
+ const quirePageId = quirePageElement.dataset.id ?? quirePageElement.id
29
+
30
+ if (webpageKey !== quirePageId) {
31
+ webpageKey = quirePageId
32
+
33
+ const title = quirePageElement.dataset.pdfCoverPageTitle
34
+
35
+ let data = { id: webpageKey, startPage: p.position, endPage: p.position, title, }
36
+
37
+ pageMap[webpageKey] = data
38
+
39
+ } else {
40
+ pageMap[webpageKey].endPage = p.position
41
+ }
42
+ }
43
+
44
+ window.pageMap = pageMap // eslint-disable-line no-undef
45
+
46
+ }
47
+ }
48
+
49
+ Paged.registerHandlers(pageTableMapper) // eslint-disable-line no-undef
@@ -1,23 +1,97 @@
1
1
  import { execa } from 'execa'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ import { splitPdf } from './split.js'
6
+ import fs from 'fs-extra'
7
+
2
8
  import which from '#helpers/which.js'
3
9
 
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = path.dirname(__filename)
12
+
4
13
  /**
5
14
  * A façade module for interacting with the Prince CLI.
6
15
  * @see https://www.princexml.com/doc/command-line/
7
16
  */
8
- export default async (input, output, options = {}) => {
17
+ export default async (publicationInput, coversInput, output, options = {}) => {
9
18
  which('prince')
10
19
 
11
20
  /**
12
21
  * @see https://www.princexml.com/doc/command-line/#options
13
22
  */
23
+
24
+ const { pdfConfig } = options
25
+
26
+ // These options run once to get the map pages to essays
27
+ const pageMapOptions = [
28
+ `--script=${ path.join(__dirname, 'princePlugin.js') }`,
29
+ `--output=${output}`,
30
+ ]
31
+
32
+ // These options are for the actual user-facing PDF
14
33
  const cmdOptions = [
15
34
  `--output=${output}`,
16
35
  ]
17
36
 
18
- if (options.debug) cmdOptions.push('--debug')
19
- if (options.verbose) cmdOptions.push('--verbose')
37
+ if (options.debug) {
38
+ pageMapOptions.push('--debug')
39
+ cmdOptions.push('--debug')
40
+ }
41
+
42
+ if (options.verbose) {
43
+ pageMapOptions.push('--verbose')
44
+ cmdOptions.push('--verbose')
45
+ }
46
+
47
+ const { dir } = path.parse(output)
48
+ if (!fs.existsSync(dir)) {
49
+ fs.mkdirsSync(dir)
50
+ }
51
+
52
+ // Execute the page mapping PDF build
53
+ let pageMap = {}
54
+ try {
55
+ const pageMapOutput = await execa('prince', [...pageMapOptions, publicationInput])
56
+ pageMap = JSON.parse(pageMapOutput.stdout)
57
+ } catch (err) {
58
+ console.error(`Generating the PDF page map failed with the error ${err.stderr}`)
59
+ process.exit(1)
60
+ }
61
+
62
+ let coversData
63
+
64
+ if (pdfConfig?.pagePDF?.coverPage === true) {
65
+
66
+ const coversPageMapOutput = await execa('prince', [...pageMapOptions, coversInput])
67
+ const coversMap = JSON.parse(coversPageMapOutput.stdout)
68
+
69
+ for (const pageId of Object.keys(coversMap)) {
70
+ if (pageId in pageMap) {
71
+ pageMap[pageId].coverPage = coversMap[pageId].startPage
72
+ }
73
+ }
74
+
75
+ coversData = fs.readFileSync(output,null)
76
+
77
+ }
78
+
79
+ let stderror,stdout
80
+
81
+ try {
82
+ ({ stderror, stdout } = await execa('prince', [...cmdOptions, publicationInput]))
83
+ } catch (err) {
84
+ console.error(`Printing the PDF failed with the error ${err.stderr}`)
85
+ process.exit(1)
86
+ }
87
+
88
+ const pdfData = fs.readFileSync(output,null)
89
+
90
+ let files = await splitPdf(pdfData,coversData,pageMap,pdfConfig)
91
+ Object.entries(files).forEach( async ([filePath,pagePdf]) => {
92
+ await fs.promises.writeFile(filePath,pagePdf)
93
+ .catch((error) => console.error(error))
94
+ })
20
95
 
21
- const { stderror, stdout } = await execa('prince', [...cmdOptions, input])
22
96
  return { stderror, stdout }
23
97
  }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @function generatePageMap()
3
+ *
4
+ * Walks the PDF-HTML map after printing and serializes the mapped output to STDOUT
5
+ */
6
+ function generatePageMap() {
7
+
8
+ const els = document.querySelectorAll('.quire-page[data-page-pdf=true]') // eslint-disable-line no-undef
9
+ let pageMap = {}
10
+
11
+ for (let i=0;i < els.length;i++) {
12
+
13
+ const el = els[i]
14
+ const boxes = el.getPrinceBoxes()
15
+
16
+ if (boxes.length < 1) { continue }
17
+
18
+ let data = { id: el.getAttribute('data-id') || el.id, title: el.getAttribute('data-footer-page-title'), startPage: boxes[0].pageNum - 1, endPage: boxes[boxes.length-1].pageNum - 1 }
19
+
20
+ let pageKey = el.getAttribute('data-id') || el.id
21
+
22
+ pageMap[pageKey] = data
23
+
24
+ if ( el.getAttribute('data-pdf-cover-page') !== 'true' ) {
25
+ continue
26
+ }
27
+
28
+ }
29
+
30
+ console.log(JSON.stringify(pageMap))
31
+
32
+ }
33
+
34
+ Prince.trackBoxes = true // eslint-disable-line no-undef
35
+ Prince.oncomplete = generatePageMap // eslint-disable-line no-undef
@@ -0,0 +1,61 @@
1
+ import path from 'node:path'
2
+
3
+ import { PDFDocument } from 'pdf-lib'
4
+ import { paths } from '#lib/11ty/index.js'
5
+
6
+ /**
7
+ * @function splitPdf(file,pageMap) -- sections out individual PDFs from `file` according to `pageMap`
8
+ *
9
+ * @param {ArrayBuffer} file - PDF file to split
10
+ * @param {Object} pageMap - page map to split PDf by
11
+ *
12
+ * Creates individual PDFs from by copying `file` (so boxes are already set) and stripping pages out of the range (in reverse order to retain the index sequence)
13
+ * Returns a map of file paths to PDF binary data for serialization
14
+ */
15
+
16
+ export async function splitPdf(file,coversFile,pageMap,pdfConfig) {
17
+
18
+ if (!pdfConfig) {
19
+ return {}
20
+ }
21
+
22
+ const { filename, outputDir } = pdfConfig
23
+
24
+ const pdfDoc = await PDFDocument.load(file)
25
+
26
+ const coversDoc = (coversFile !== undefined) ? await PDFDocument.load(coversFile) : undefined
27
+
28
+ let resultFiles = {}
29
+
30
+ for ( const [pageId, pageConfig] of Object.entries(pageMap) ) {
31
+ const { endPage, startPage, coverPage } = pageConfig
32
+
33
+ // TODO: Set the PDF's sectional doc metadata
34
+
35
+ const sectionDoc = await pdfDoc.copy()
36
+
37
+ for (let p=pdfDoc.getPageCount() - 1; p > endPage; --p) {
38
+ sectionDoc.removePage(p)
39
+ }
40
+ for (let q=startPage - 1; q >= 0; --q) {
41
+ sectionDoc.removePage(q)
42
+ }
43
+
44
+ if (coversDoc && coverPage !== undefined && coverPage >= 0) {
45
+ // NB: `copyPages()` sets page sizing + other metadata on the way into the target PDF
46
+ const cover = await sectionDoc.copyPages(coversDoc,[coverPage])
47
+ sectionDoc.insertPage(0,cover[0])
48
+ }
49
+
50
+ const section = await sectionDoc.save()
51
+
52
+ const sectionId = pageId.replace(/^page-/g,'')
53
+ const sectionFn = `${filename}-${sectionId}.pdf`
54
+ const sectionFp = path.join( paths.output, outputDir, sectionFn )
55
+
56
+ resultFiles[sectionFp] = section
57
+ }
58
+
59
+ return resultFiles
60
+ }
61
+