From e910b3e54d86ed237822607cf885dcdcbe9f60f2 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 15 Aug 2024 19:41:36 -0400 Subject: [PATCH 01/11] extracted code from ipynb files and rendered as python --- main.ts | 39 +++++++++++++++++++++++++++++++++++---- manifest.json | 2 +- settings.ts | 19 ++++++++++++++++--- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/main.ts b/main.ts index edddc70..ed54325 100644 --- a/main.ts +++ b/main.ts @@ -30,6 +30,36 @@ export default class EmbedCodeFile extends Plugin { await this.saveData(this.settings); } + //TODO: split functionality into separate file + convertLang(lang: string): string { + if (lang === 'ipynb' && this.settings.displayIpynbAsPython) { + return 'python' + } + return lang + } + + parseSpecial(lang: string, src: string): string { + if (lang === 'ipynb' && this.settings.displayIpynbAsPython) { + let code = "" + try { + let notebook = JSON.parse(src); + if (Array.isArray(notebook.cells)) { + notebook.cells.forEach((cell: any) => { + if (cell.cell_type === 'code' && Array.isArray(cell.source)) { + code += cell.source.join('') + } + }); + } else { + console.error('No cells found in the notebook.'); + } + } catch (error) { + console.error("Invalid JSON string:", error); + } + return code + } + return src + } + async registerRenderer(lang: string) { this.registerMarkdownCodeBlockProcessor(`embed-${lang}`, async (meta, el, ctx) => { let fullSrc = "" @@ -80,11 +110,11 @@ export default class EmbedCodeFile extends Plugin { if (srcLinesNumString) { srcLinesNum = analyseSrcLines(srcLinesNumString) } - + let parsedSrc = this.parseSpecial(lang, fullSrc) if (srcLinesNum.length == 0) { - src = fullSrc + src = parsedSrc } else { - src = extractSrcLines(fullSrc, srcLinesNum) + src = extractSrcLines(parsedSrc, srcLinesNum) } let title = metaYaml.TITLE @@ -92,7 +122,8 @@ export default class EmbedCodeFile extends Plugin { title = srcPath } - await MarkdownRenderer.renderMarkdown('```' + lang + '\n' + src + '\n```', el, '', this) + let srcBlock = '```' + this.convertLang(lang) + '\n' + src + '\n```' + await MarkdownRenderer.renderMarkdown(srcBlock, el, '', this) this.addTitleLivePreview(el, title); }); } diff --git a/manifest.json b/manifest.json index 679e61c..07af515 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "id": "embed-code-file", + "id": "embed-code-file-ipynb-as-python", "name": "Embed Code File", "version": "1.2.0", "minAppVersion": "0.15.0", diff --git a/settings.ts b/settings.ts index 560673a..0269684 100644 --- a/settings.ts +++ b/settings.ts @@ -6,12 +6,14 @@ export interface EmbedCodeFileSettings { includedLanguages: string; titleBackgroundColor: string; titleFontColor: string; + displayIpynbAsPython: boolean; } export const DEFAULT_SETTINGS: EmbedCodeFileSettings = { - includedLanguages: 'c,cs,cpp,java,python,go,ruby,javascript,js,typescript,ts,shell,sh,bash', + includedLanguages: 'c,cs,cpp,java,python,go,ruby,javascript,js,typescript,ts,shell,sh,bash,ipynb', titleBackgroundColor: "#00000020", - titleFontColor: "" + titleFontColor: "", + displayIpynbAsPython: true } export class EmbedCodeFileSettingTab extends PluginSettingTab { @@ -48,7 +50,7 @@ export class EmbedCodeFileSettingTab extends PluginSettingTab { this.plugin.settings.titleFontColor = value; await this.plugin.saveSettings(); })); - + new Setting(containerEl) .setName('Background color of title') .addText(text => text @@ -58,5 +60,16 @@ export class EmbedCodeFileSettingTab extends PluginSettingTab { this.plugin.settings.titleBackgroundColor = value; await this.plugin.saveSettings(); })); + + new Setting(containerEl) + .setName('Display .ipynb as python') + .setDesc('Parse .ipynb files to extract and display their python source code.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.displayIpynbAsPython) + .onChange(async (value) => { + this.plugin.settings.displayIpynbAsPython = value; + await this.plugin.saveSettings(); + })); + } } From 9a7a9eae1765feeaafe9c398d0a3705fe8df3b38 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 15 Aug 2024 20:12:49 -0400 Subject: [PATCH 02/11] Add release workflow --- .github/workflows/release.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8272408 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Release Obsidian plugin + +on: + push: + tags: + - "*" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "18.x" + + - name: Build plugin + run: | + npm install + npm run build + + - name: Create release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + tag="${GITHUB_REF#refs/tags/}" + + gh release create "$tag" \ + --title="$tag" \ + --draft \ + main.js manifest.json styles.css From 5a555ad74deab82b332e96707ec488d1731470c1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 15 Aug 2024 20:19:46 -0400 Subject: [PATCH 03/11] updated version number and plugin name to distinguish from upstream --- manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 07af515..6ca60f7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "embed-code-file-ipynb-as-python", - "name": "Embed Code File", - "version": "1.2.0", + "name": "Embed Code File with Jupyter Notebook Support", + "version": "0.0.2", "minAppVersion": "0.15.0", "description": "This is a plugin for Obsidian that allows for embedding code files.", "author": "Obsidian", From 7e797d279882571db084a80fc0a8588ea2db19ab Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 00:20:50 -0400 Subject: [PATCH 04/11] moved custom parsing into a separate file and used a factory method to improve extensibility --- code-parser.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.ts | 36 +++---------------------- 2 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 code-parser.ts diff --git a/code-parser.ts b/code-parser.ts new file mode 100644 index 0000000..6abee0c --- /dev/null +++ b/code-parser.ts @@ -0,0 +1,71 @@ +import { EmbedCodeFileSettings } from "./settings"; + +export interface CodeParser { + parseCode: (code: string) => string; + getOutputLanguage: () => string; +} + +//Factory method for future complex code types +export function createCodeParser(inputLanguage: string, settings: EmbedCodeFileSettings): CodeParser { + switch (inputLanguage) { + case "ipynb": + return new IpynbParser(settings.displayIpynbAsPython); + break; + default: + return new DefaultParser(inputLanguage); + } +} + +export class IpynbParser implements CodeParser { + private enabled: boolean; + + constructor(enabled: boolean) { + this.enabled = enabled; + } + + parseCode(src: string): string { + if (!this.enabled) { + return src; + } + + let code = "" + try { + let notebook = JSON.parse(src); + if (Array.isArray(notebook.cells)) { + notebook.cells.forEach((cell: any) => { + if (cell.cell_type === 'code' && Array.isArray(cell.source)) { + code += cell.source.join('') + } + }); + } else { + console.error('No cells found in the notebook.'); + } + } catch (error) { + console.error("Invalid JSON string:", error); + } + return code + } + + getOutputLanguage(): string { + if (!this.enabled) { + return 'ipynb' + } + return 'python' + } +} + +export class DefaultParser implements CodeParser { + private inputLanguage: string; + + constructor(inputLanguage: string) { + this.inputLanguage = inputLanguage; + } + + parseCode(src: string): string { + return src; + } + + getOutputLanguage(): string { + return this.inputLanguage; + } +} diff --git a/main.ts b/main.ts index ed54325..9554086 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,7 @@ import { Plugin, MarkdownRenderer, TFile, MarkdownPostProcessorContext, MarkdownView, parseYaml, requestUrl} from 'obsidian'; import { EmbedCodeFileSettings, EmbedCodeFileSettingTab, DEFAULT_SETTINGS} from "./settings"; import { analyseSrcLines, extractSrcLines} from "./utils"; +import { createCodeParser, CodeParser } from "./code-parser.ts"; export default class EmbedCodeFile extends Plugin { settings: EmbedCodeFileSettings; @@ -30,40 +31,11 @@ export default class EmbedCodeFile extends Plugin { await this.saveData(this.settings); } - //TODO: split functionality into separate file - convertLang(lang: string): string { - if (lang === 'ipynb' && this.settings.displayIpynbAsPython) { - return 'python' - } - return lang - } - - parseSpecial(lang: string, src: string): string { - if (lang === 'ipynb' && this.settings.displayIpynbAsPython) { - let code = "" - try { - let notebook = JSON.parse(src); - if (Array.isArray(notebook.cells)) { - notebook.cells.forEach((cell: any) => { - if (cell.cell_type === 'code' && Array.isArray(cell.source)) { - code += cell.source.join('') - } - }); - } else { - console.error('No cells found in the notebook.'); - } - } catch (error) { - console.error("Invalid JSON string:", error); - } - return code - } - return src - } - async registerRenderer(lang: string) { this.registerMarkdownCodeBlockProcessor(`embed-${lang}`, async (meta, el, ctx) => { let fullSrc = "" let src = "" + let codeParser: CodeParser = createCodeParser(lang, this.settings) let metaYaml: any try { @@ -110,7 +82,7 @@ export default class EmbedCodeFile extends Plugin { if (srcLinesNumString) { srcLinesNum = analyseSrcLines(srcLinesNumString) } - let parsedSrc = this.parseSpecial(lang, fullSrc) + let parsedSrc = codeParser.parseCode(fullSrc) if (srcLinesNum.length == 0) { src = parsedSrc } else { @@ -122,7 +94,7 @@ export default class EmbedCodeFile extends Plugin { title = srcPath } - let srcBlock = '```' + this.convertLang(lang) + '\n' + src + '\n```' + let srcBlock = '```' + codeParser.getOutputLanguage() + '\n' + src + '\n```' await MarkdownRenderer.renderMarkdown(srcBlock, el, '', this) this.addTitleLivePreview(el, title); }); From fc3508442ed59b9d7b3de1d2c6aacc17a1bdd998 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 01:25:01 -0400 Subject: [PATCH 05/11] added block numbers and spacing to python output --- code-parser.ts | 44 +++++++++++++++++++++++++++++++++----------- main.ts | 3 ++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/code-parser.ts b/code-parser.ts index 6abee0c..3a4f956 100644 --- a/code-parser.ts +++ b/code-parser.ts @@ -29,20 +29,42 @@ export class IpynbParser implements CodeParser { } let code = "" + + let notebook; try { - let notebook = JSON.parse(src); - if (Array.isArray(notebook.cells)) { - notebook.cells.forEach((cell: any) => { - if (cell.cell_type === 'code' && Array.isArray(cell.source)) { - code += cell.source.join('') - } - }); - } else { - console.error('No cells found in the notebook.'); - } + notebook = JSON.parse(src); } catch (error) { - console.error("Invalid JSON string:", error); + return "Improperly formatted .ipynb file"; + } + + let codeBlocks: string[] = []; + if (Array.isArray(notebook.cells) && notebook.cells.length !== 0) { + notebook.cells.forEach((cell: any) => { + if (cell.cell_type === 'code' && Array.isArray(cell.source)) { + let codeBlock = cell.source.join(''); + if (codeBlock.length > 0) { + codeBlocks.push(codeBlock); + } + } + }); + } else { + return "No cells found in the notebook"; } + + if (codeBlocks.length === 0) { + return "No code cells in the notebook"; + } + + codeBlocks.forEach((block: string, index: number) => { + if (codeBlocks.length != 1) { + code += `# Cell ${index + 1}:\n\n`; + } + code += block; + if (index !== codeBlocks.length - 1) { + code += "\n\n"; + } + }); + return code } diff --git a/main.ts b/main.ts index 9554086..b5d274b 100644 --- a/main.ts +++ b/main.ts @@ -82,7 +82,8 @@ export default class EmbedCodeFile extends Plugin { if (srcLinesNumString) { srcLinesNum = analyseSrcLines(srcLinesNumString) } - let parsedSrc = codeParser.parseCode(fullSrc) + + let parsedSrc: string = codeParser.parseCode(fullSrc) if (srcLinesNum.length == 0) { src = parsedSrc } else { From 50f454ce42f735298d6dc10d1394ddaf16a144bd Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 01:51:03 -0400 Subject: [PATCH 06/11] added code blocks formatting settings --- code-parser.ts | 13 +++++++++---- settings.ts | 18 +++++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/code-parser.ts b/code-parser.ts index 3a4f956..8c76505 100644 --- a/code-parser.ts +++ b/code-parser.ts @@ -9,7 +9,7 @@ export interface CodeParser { export function createCodeParser(inputLanguage: string, settings: EmbedCodeFileSettings): CodeParser { switch (inputLanguage) { case "ipynb": - return new IpynbParser(settings.displayIpynbAsPython); + return new IpynbParser(settings.displayIpynbAsPython, settings.showIpynbCellNumbers); break; default: return new DefaultParser(inputLanguage); @@ -18,9 +18,11 @@ export function createCodeParser(inputLanguage: string, settings: EmbedCodeFileS export class IpynbParser implements CodeParser { private enabled: boolean; + private cellNumbers: boolean; - constructor(enabled: boolean) { + constructor(enabled: boolean, cellNumbers: boolean) { this.enabled = enabled; + this.cellNumbers = cellNumbers; } parseCode(src: string): string { @@ -56,12 +58,15 @@ export class IpynbParser implements CodeParser { } codeBlocks.forEach((block: string, index: number) => { - if (codeBlocks.length != 1) { + if (codeBlocks.length != 1 && this.cellNumbers) { code += `# Cell ${index + 1}:\n\n`; } code += block; if (index !== codeBlocks.length - 1) { - code += "\n\n"; + code += "\n"; + if (this.cellNumbers) { + code += "\n"; + } } }); diff --git a/settings.ts b/settings.ts index 0269684..825391f 100644 --- a/settings.ts +++ b/settings.ts @@ -7,13 +7,15 @@ export interface EmbedCodeFileSettings { titleBackgroundColor: string; titleFontColor: string; displayIpynbAsPython: boolean; + showIpynbCellNumbers: boolean; } export const DEFAULT_SETTINGS: EmbedCodeFileSettings = { includedLanguages: 'c,cs,cpp,java,python,go,ruby,javascript,js,typescript,ts,shell,sh,bash,ipynb', titleBackgroundColor: "#00000020", titleFontColor: "", - displayIpynbAsPython: true + displayIpynbAsPython: true, + showIpynbCellNumbers: true, } export class EmbedCodeFileSettingTab extends PluginSettingTab { @@ -62,8 +64,8 @@ export class EmbedCodeFileSettingTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Display .ipynb as python') - .setDesc('Parse .ipynb files to extract and display their python source code.') + .setName('Display Jupyter Notebooks as python') + .setDesc('Parse .ipynb files to extract and display their python source code.') .addToggle(toggle => toggle .setValue(this.plugin.settings.displayIpynbAsPython) .onChange(async (value) => { @@ -71,5 +73,15 @@ export class EmbedCodeFileSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); })); + new Setting(containerEl) + .setName('Jupyter Notebook Cell Numbers') + .setDesc('Show Jupyter Notebook cell numbers in the embedded python code.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.showIpynbCellNumbers) + .onChange(async (value) => { + this.plugin.settings.showIpynbCellNumbers = value; + await this.plugin.saveSettings(); + })); + } } From adef472be0c65ae3fa3de17d1f37fbd117122ae3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 02:16:23 -0400 Subject: [PATCH 07/11] improved error handling and made messages uniform --- code-parser.ts | 10 +++++----- main.ts | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/code-parser.ts b/code-parser.ts index 8c76505..1b2cd5f 100644 --- a/code-parser.ts +++ b/code-parser.ts @@ -1,7 +1,7 @@ import { EmbedCodeFileSettings } from "./settings"; export interface CodeParser { - parseCode: (code: string) => string; + parseCode: (code: string) => string | Error; getOutputLanguage: () => string; } @@ -25,7 +25,7 @@ export class IpynbParser implements CodeParser { this.cellNumbers = cellNumbers; } - parseCode(src: string): string { + parseCode(src: string): string | Error { if (!this.enabled) { return src; } @@ -36,7 +36,7 @@ export class IpynbParser implements CodeParser { try { notebook = JSON.parse(src); } catch (error) { - return "Improperly formatted .ipynb file"; + return new Error("Improperly formatted .ipynb file"); } let codeBlocks: string[] = []; @@ -50,11 +50,11 @@ export class IpynbParser implements CodeParser { } }); } else { - return "No cells found in the notebook"; + return new Error("No cells found in the notebook"); } if (codeBlocks.length === 0) { - return "No code cells in the notebook"; + return new Error("No code cells in the notebook"); } codeBlocks.forEach((block: string, index: number) => { diff --git a/main.ts b/main.ts index b5d274b..1bbc47e 100644 --- a/main.ts +++ b/main.ts @@ -83,7 +83,14 @@ export default class EmbedCodeFile extends Plugin { srcLinesNum = analyseSrcLines(srcLinesNumString) } + let parsedSrc: string = codeParser.parseCode(fullSrc) + if (parsedSrc instanceof Error) { + const errMsg = `\`ERROR: ${parsedSrc.message}\`` + await MarkdownRenderer.renderMarkdown(errMsg, el, '', this) + return + } + if (srcLinesNum.length == 0) { src = parsedSrc } else { From c90cc964b0a6c92f5611eb6d904305e909c3f1ad Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 02:17:00 -0400 Subject: [PATCH 08/11] updated version 1.0.0 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 6ca60f7..3606ab2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "embed-code-file-ipynb-as-python", "name": "Embed Code File with Jupyter Notebook Support", - "version": "0.0.2", + "version": "1.0.0", "minAppVersion": "0.15.0", "description": "This is a plugin for Obsidian that allows for embedding code files.", "author": "Obsidian", From fb63d2d848fa9fd15cf477cc21cb9c6726425c76 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 02:21:32 -0400 Subject: [PATCH 09/11] fixed build errors --- main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index 1bbc47e..62aee78 100644 --- a/main.ts +++ b/main.ts @@ -1,7 +1,7 @@ import { Plugin, MarkdownRenderer, TFile, MarkdownPostProcessorContext, MarkdownView, parseYaml, requestUrl} from 'obsidian'; import { EmbedCodeFileSettings, EmbedCodeFileSettingTab, DEFAULT_SETTINGS} from "./settings"; import { analyseSrcLines, extractSrcLines} from "./utils"; -import { createCodeParser, CodeParser } from "./code-parser.ts"; +import { createCodeParser, CodeParser } from "./code-parser"; export default class EmbedCodeFile extends Plugin { settings: EmbedCodeFileSettings; @@ -84,7 +84,7 @@ export default class EmbedCodeFile extends Plugin { } - let parsedSrc: string = codeParser.parseCode(fullSrc) + let parsedSrc: any = codeParser.parseCode(fullSrc) if (parsedSrc instanceof Error) { const errMsg = `\`ERROR: ${parsedSrc.message}\`` await MarkdownRenderer.renderMarkdown(errMsg, el, '', this) From b4f5a231c286167873f26678e0b4eb27b2a31249 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 02:22:28 -0400 Subject: [PATCH 10/11] updated manifest to 1.0.1 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 3606ab2..6c55961 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "embed-code-file-ipynb-as-python", "name": "Embed Code File with Jupyter Notebook Support", - "version": "1.0.0", + "version": "1.0.1", "minAppVersion": "0.15.0", "description": "This is a plugin for Obsidian that allows for embedding code files.", "author": "Obsidian", From ddbbfbb5cc4699ea97b4f55cdb51adecd587cead Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 16 Aug 2024 02:28:10 -0400 Subject: [PATCH 11/11] reverted manifest to match upstream --- manifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 6c55961..679e61c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { - "id": "embed-code-file-ipynb-as-python", - "name": "Embed Code File with Jupyter Notebook Support", - "version": "1.0.1", + "id": "embed-code-file", + "name": "Embed Code File", + "version": "1.2.0", "minAppVersion": "0.15.0", "description": "This is a plugin for Obsidian that allows for embedding code files.", "author": "Obsidian",