diff --git a/README.md b/README.md index 97b36fd..973db50 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,18 @@ And here's the result: ![](jupyter-lorenz-notebook.png) +## Document attributes + +| Name | Default Value | Mapping | +|----------------------------|---------------|----------------------------------| +| `jupyter-language-name` | `python` | `metadata.language_info.name` | +| `jupyter-language-version` | `3.9.1` | `metadata.language_info.version` | +| `jupyter-kernel-name` | `python3` | `metadata.kernelspec.name` | +| `jupyter-kernel-language` | `python` | `metadata.kernelspec.language` | + +**IMPORTANT:** The language name defined in `jupyter-language-name` will be used to decide which AsciiDoc source blocks will be converted to Notebook code cells and which will be converted to Markdown cells. +For instance, if the Jupyter language name is `python` the converter will convert source blocks that have the language `python` to code cells. Source blocks with other languages will be converted as Markdown cells. + ## Notebook file format This converter generates [Jupyter notebooks] using [Notebook file format](https://nbformat.readthedocs.io/en/latest/format_description.html) version 4.4. diff --git a/src/index.js b/src/index.js index e42bd0e..8fc8731 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,8 @@ class JupyterConverter { this.ignoredNodes = [] const languageName = node.getAttribute('jupyter-language-name', 'python') const languageVersion = node.getAttribute('jupyter-language-version', '3.9.1') + const kernelName = node.getAttribute('jupyter-kernel-name', 'python3') + const kernelLanguage = node.getAttribute('jupyter-kernel-language', 'python') const blocks = node.getBlocks() const cells = [] let lastCell = {} @@ -49,6 +51,10 @@ class JupyterConverter { language_info: { name: languageName, version: languageVersion + }, + kernelspec: { + name: kernelName, + language: kernelLanguage } }, nbformat: 4, @@ -158,7 +164,16 @@ class JupyterConverter { const lines = node.lines const source = lines.map((l) => l + '\n') const language = node.getAttribute('language') - if (language === 'python' || language === 'py') { + const languageName = node.getDocument().getAttribute('jupyter-language-name', 'python') + let languages + if (languageName === 'python' || languageName === 'py') { + languages = ['python', 'py'] + } else if (languageName === 'c++' || languageName === 'cpp') { + languages = ['c++', 'cpp'] + } else { + languages = [languageName] + } + if (languages.includes(language)) { return [{ cell_type: 'code', execution_count: 0, diff --git a/test/converter.spec.js b/test/converter.spec.js index 4b8eb74..6069d37 100644 --- a/test/converter.spec.js +++ b/test/converter.spec.js @@ -30,6 +30,8 @@ describe('Jupyter converter', () => { const ipynb = JSON.parse(result) expect(ipynb.metadata.language_info.name).is.equal('python') expect(ipynb.metadata.language_info.version).is.equal('2.7.10') + expect(ipynb.metadata.kernelspec.name).is.equal('python3') + expect(ipynb.metadata.kernelspec.language).is.equal('python') expect(ipynb.cells.length).is.equal(32) const codeCells = ipynb.cells.filter(cell => cell.cell_type === 'code') expect(codeCells.length).is.equal(21) @@ -37,6 +39,126 @@ describe('Jupyter converter', () => { graph = Graph() `) + }) + it('should configure language with document attributes', async () => { + const result = asciidoctor.convert(`= Hello World +:jupyter-language-name: c++ +:jupyter-language-version: 17 + +`, { backend: 'jupyter' }) + expect(result).is.not.empty() + const ipynb = JSON.parse(result) + expect(ipynb.metadata.language_info.name).is.equal('c++') + expect(ipynb.metadata.language_info.version).is.equal('17') + }) + it('should configure kernelspec with document attributes', async () => { + const result = asciidoctor.convert(`= Hello World +:jupyter-language-name: c++ +:jupyter-language-version: 17 +:jupyter-kernel-name: xcpp17 +:jupyter-kernel-language: C++17 + +`, { backend: 'jupyter' }) + expect(result).is.not.empty() + const ipynb = JSON.parse(result) + expect(ipynb.metadata.language_info.name).is.equal('c++') + expect(ipynb.metadata.language_info.version).is.equal('17') + expect(ipynb.metadata.kernelspec.name).is.equal('xcpp17') + expect(ipynb.metadata.kernelspec.language).is.equal('C++17') + }) + it('should convert source blocks depending on language name (C++)', async () => { + const result = asciidoctor.convert(`= Hello World +:jupyter-language-name: c++ +:jupyter-language-version: 17 +:jupyter-kernel-name: xcpp17 +:jupyter-kernel-language: C++17 + +.Python +[source,py] +---- +print('hello') +---- + +.C{pp} +[source,cpp] +---- +int i=1; +---- +`, { backend: 'jupyter' }) + expect(result).is.not.empty() + const ipynb = JSON.parse(result) + expect(ipynb.cells.length).is.equal(2) + expect(ipynb.cells[0].cell_type).is.equal('markdown') + expect(ipynb.cells[0].source.join('')).is.equal(`# Hello World + +\`\`\`py +print('hello') +\`\`\``) + expect(ipynb.cells[1].cell_type).is.equal('code') + expect(ipynb.cells[1].source.join('')).is.equal(`int i=1; +`) + }) + it('should convert source blocks depending on language name (Python)', async () => { + const result = asciidoctor.convert(`= Hello World +:jupyter-language-name: python +:jupyter-language-version: 3.11.5 + +.Python +[source,py] +---- +print('hello') +---- + +.C{pp} +[source,cpp] +---- +int i=1; +---- +`, { backend: 'jupyter' }) + expect(result).is.not.empty() + const ipynb = JSON.parse(result) + expect(ipynb.cells.length).is.equal(3) + expect(ipynb.cells[0].cell_type).is.equal('markdown') + expect(ipynb.cells[0].source.join('')).is.equal(`# Hello World + +`) + expect(ipynb.cells[1].cell_type).is.equal('code') + expect(ipynb.cells[1].source.join('')).is.equal(`print('hello') +`) + expect(ipynb.cells[2].cell_type).is.equal('markdown') + expect(ipynb.cells[2].source.join('')).is.equal(`\`\`\`cpp +int i=1; +\`\`\``) + }) + it('should convert source blocks depending on language name (default -> Python)', async () => { + const result = asciidoctor.convert(`= Hello World + +.Python +[source,py] +---- +print('hello') +---- + +.C{pp} +[source,cpp] +---- +int i=1; +---- +`, { backend: 'jupyter' }) + expect(result).is.not.empty() + const ipynb = JSON.parse(result) + expect(ipynb.cells.length).is.equal(3) + expect(ipynb.cells[0].cell_type).is.equal('markdown') + expect(ipynb.cells[0].source.join('')).is.equal(`# Hello World + +`) + expect(ipynb.cells[1].cell_type).is.equal('code') + expect(ipynb.cells[1].source.join('')).is.equal(`print('hello') +`) + expect(ipynb.cells[2].cell_type).is.equal('markdown') + expect(ipynb.cells[2].source.join('')).is.equal(`\`\`\`cpp +int i=1; +\`\`\``) }) it('should convert an exercise guide to ipynb', async () => { const inputFile = path.join(__dirname, 'fixtures', 'intro-neo4j-guides-01.adoc')