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 diff --git a/code-parser.ts b/code-parser.ts new file mode 100644 index 0000000..1b2cd5f --- /dev/null +++ b/code-parser.ts @@ -0,0 +1,98 @@ +import { EmbedCodeFileSettings } from "./settings"; + +export interface CodeParser { + parseCode: (code: string) => string | Error; + 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, settings.showIpynbCellNumbers); + break; + default: + return new DefaultParser(inputLanguage); + } +} + +export class IpynbParser implements CodeParser { + private enabled: boolean; + private cellNumbers: boolean; + + constructor(enabled: boolean, cellNumbers: boolean) { + this.enabled = enabled; + this.cellNumbers = cellNumbers; + } + + parseCode(src: string): string | Error { + if (!this.enabled) { + return src; + } + + let code = "" + + let notebook; + try { + notebook = JSON.parse(src); + } catch (error) { + return new Error("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 new Error("No cells found in the notebook"); + } + + if (codeBlocks.length === 0) { + return new Error("No code cells in the notebook"); + } + + codeBlocks.forEach((block: string, index: number) => { + if (codeBlocks.length != 1 && this.cellNumbers) { + code += `# Cell ${index + 1}:\n\n`; + } + code += block; + if (index !== codeBlocks.length - 1) { + code += "\n"; + if (this.cellNumbers) { + code += "\n"; + } + } + }); + + 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 edddc70..62aee78 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"; export default class EmbedCodeFile extends Plugin { settings: EmbedCodeFileSettings; @@ -34,6 +35,7 @@ export default class EmbedCodeFile extends Plugin { this.registerMarkdownCodeBlockProcessor(`embed-${lang}`, async (meta, el, ctx) => { let fullSrc = "" let src = "" + let codeParser: CodeParser = createCodeParser(lang, this.settings) let metaYaml: any try { @@ -81,10 +83,18 @@ export default class EmbedCodeFile extends Plugin { srcLinesNum = analyseSrcLines(srcLinesNumString) } + + let parsedSrc: any = codeParser.parseCode(fullSrc) + if (parsedSrc instanceof Error) { + const errMsg = `\`ERROR: ${parsedSrc.message}\`` + await MarkdownRenderer.renderMarkdown(errMsg, el, '', this) + return + } + if (srcLinesNum.length == 0) { - src = fullSrc + src = parsedSrc } else { - src = extractSrcLines(fullSrc, srcLinesNum) + src = extractSrcLines(parsedSrc, srcLinesNum) } let title = metaYaml.TITLE @@ -92,7 +102,8 @@ export default class EmbedCodeFile extends Plugin { title = srcPath } - await MarkdownRenderer.renderMarkdown('```' + lang + '\n' + src + '\n```', el, '', this) + let srcBlock = '```' + codeParser.getOutputLanguage() + '\n' + src + '\n```' + await MarkdownRenderer.renderMarkdown(srcBlock, el, '', this) this.addTitleLivePreview(el, title); }); } diff --git a/settings.ts b/settings.ts index 560673a..825391f 100644 --- a/settings.ts +++ b/settings.ts @@ -6,12 +6,16 @@ export interface EmbedCodeFileSettings { includedLanguages: string; 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', + includedLanguages: 'c,cs,cpp,java,python,go,ruby,javascript,js,typescript,ts,shell,sh,bash,ipynb', titleBackgroundColor: "#00000020", - titleFontColor: "" + titleFontColor: "", + displayIpynbAsPython: true, + showIpynbCellNumbers: true, } export class EmbedCodeFileSettingTab extends PluginSettingTab { @@ -48,7 +52,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 +62,26 @@ export class EmbedCodeFileSettingTab extends PluginSettingTab { this.plugin.settings.titleBackgroundColor = value; await this.plugin.saveSettings(); })); + + new Setting(containerEl) + .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) => { + this.plugin.settings.displayIpynbAsPython = value; + 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(); + })); + } }