diff --git a/brian2tools/mdexport/expander.py b/brian2tools/mdexport/expander.py index 62f5586f..0cba6473 100644 --- a/brian2tools/mdexport/expander.py +++ b/brian2tools/mdexport/expander.py @@ -8,6 +8,8 @@ from sympy.printing import latex from sympy.abc import * from markdown_strings import * +from jinja2 import Template +from .template import templates import numpy as np import re import inspect @@ -284,7 +286,7 @@ def render_expression(self, expression, differential=False): # to remove `$` (in most md compiler single $ is used) return rend_exp[1:][:-1] - def create_md_string(self, net_dict): + def create_md_string(self, net_dict, template_name): """ Create markdown text by checking the standard dictionary and call required expand functions and arrange the descriptions @@ -373,6 +375,8 @@ def create_md_string(self, net_dict): for connector in initializers_connectors if connector['type'] == 'connect' and connector['synapses'] == obj_mem['name']] + if obj_key == 'neurongroup' and current_order == 1: + obj_mem['template_name'] = template_name run_string += ('- ' + func_map[obj_key]['f'](obj_mem)) @@ -478,6 +482,7 @@ def expand_NeuronGroup(self, neurongrp): neurongrp : dict Standard dictionary of NeuronGroup """ + template_name = neurongrp['template_name'] # start expanding md_str = '' # name and size @@ -508,8 +513,18 @@ def expand_NeuronGroup(self, neurongrp): self.check_plural(neurongrp['run_regularly']) + ': ' + endll) for run_reg in neurongrp['run_regularly']: md_str += self.expand_runregularly(run_reg) - + + # Create Jinja2 Template object + if template_name not in templates : + print(md_str) + return md_str + # Create Jinja2 Template object + template = Template(templates[template_name]) + # # Render the template with the provided NeuronGroup dictionary + md_str = template.render(neurongrp=neurongrp) + print (md_str) return md_str + def expand_SpikeSource(self, source): """ @@ -862,6 +877,7 @@ def expand_StateMonitor(self, statemon): md_str += (', for member' + self.check_plural(statemon['record']) + ': ' + + ','.join([str(ind) for ind in statemon['record']])) return md_str + endll @@ -907,6 +923,7 @@ def expand_EventMonitor(self, eventmon): ','.join([str(ind) for ind in eventmon['record']])) md_str += (' when event ' + bold(eventmon['event']) + ' is triggered') + return md_str + endll def expand_PopulationRateMonitor(self, popratemon): diff --git a/brian2tools/mdexport/mdexporter.py b/brian2tools/mdexport/mdexporter.py index 70d42ac5..89a756dc 100644 --- a/brian2tools/mdexport/mdexporter.py +++ b/brian2tools/mdexport/mdexporter.py @@ -2,6 +2,7 @@ from brian2tools.baseexport.device import BaseExporter import os import inspect +import subprocess from .expander import * @@ -13,7 +14,7 @@ class MdExporter(BaseExporter): """ def build(self, direct_call=True, debug=False, expander=None, - filename=None): + filename=None, additional_formats=None, template_type=None): """ Build the exporter @@ -34,6 +35,15 @@ def build(self, direct_call=True, debug=False, expander=None, If mentioned, the markdown text would be written in that particular filename. When empty string '' is passed the user file name would be taken + + additional_formats : str or list of str or all + If user wants to have the output file in additional_formats they + can specify them under this variable and the options are pdf, + latex, html and all. + + template_type : str + Based on your selected template, it will rendered otherwise + a default template will be used for rendering """ # buil_on_run = True but called build() directly if self.build_on_run and direct_call: @@ -70,7 +80,7 @@ def build(self, direct_call=True, debug=False, expander=None, self.expander = MdExpander() # start creating markdown descriptions using expander - self.md_text = self.expander.create_md_string(self.runs) + self.md_text = self.expander.create_md_string(self.runs, template_type) # check output filename if filename: @@ -90,9 +100,33 @@ def build(self, direct_call=True, debug=False, expander=None, print(self.md_text) elif self.filename: # start writing markdown text in file - md_file = open(self.filename + '.md', "w") + source_file = self.filename + ".md" + md_file = open(source_file, "w") md_file.write(self.md_text) md_file.close() + + # Check if Pandoc is installed + try: + subprocess.check_call(["pandoc", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + raise Exception("Pandoc is not installed. Please install Pandoc and try again.") + + formats_extensions = {'latex':'.tex', 'html':'.html', 'pdf':'.pdf'} + if isinstance(additional_formats, str): + if additional_formats == "all": + formats = ['latex', 'html', 'pdf'] + else: + formats = [additional_formats] + else: + formats = additional_formats if additional_formats is not None else [] + for format_name in formats: + filename = self.filename + formats_extensions[format_name] + try: + subprocess.run(["pandoc", "--from", "markdown", "--to", format_name, "-o", filename, source_file], + check=True) + print("Conversion complete! Files generated:", filename) + except subprocess.CalledProcessError as ex: + print(f"Could not generate format '{format_name}': {str(ex)}") else: pass # do nothing diff --git a/brian2tools/mdexport/template.py b/brian2tools/mdexport/template.py new file mode 100644 index 00000000..c9445716 --- /dev/null +++ b/brian2tools/mdexport/template.py @@ -0,0 +1,54 @@ +# Define the Jinja2 template string +templates = { + 'template_str' : """ + ## Network details + + **Neuron population:** + - Group {{ neurongrp.name }}, consisting of {{ neurongrp.N }} neurons. + + # Model dynamics + {% for key, eqn in neurongrp.equations.items() %} + - $\frac{d}{d t} {{ key }}$ = {{ eqn.expr }}{% if eqn.unit %} [{{ eqn.unit }}]{% endif %} + {% endfor %} + + {% if neurongrp.user_method %} + The equations are integrated with the '{{ neurongrp.user_method }}' method. + {% endif %} + + # Events (if present) + {% if 'events' in neurongrp %} + **Events:** + + {% for event, details in neurongrp.events.items() %} + - If {{ details.threshold.code }}, a {{ event }} event is triggered and {{ details.reset.code }}. + {% endfor %} + {% endif %} + + # Constants (if present) + {% if 'identifiers' in neurongrp %} + **Constants:** + + {% for identifier, value in neurongrp.identifiers.items() %} + - {{ identifier }}: {{ value }} + {% endfor %} + {% endif %} + + # Initial values (if present) + {% if 'initializer' in neurongrp and neurongrp['initializer'] %} + **Initial values:** + + {% for initializer in neurongrp['initializer'] %} + - {{ initializer.variable }}: {{ initializer.value }}{% if initializer.unit %} [{{ initializer.unit }}]{% endif %} + {% endfor %} + {% endif %} + + # Run regularly (if present) + {% if 'run_regularly' in neurongrp %} + **Run regularly:** + + {% for run_reg in neurongrp['run_regularly'] %} + - {{ run_reg }} + {% endfor %} + {% endif %} +""" +}