<?xml version="1.0" encoding="utf-8"?>
<!-- Created by Leo: https://leo-editor.github.io/leo-editor/leo_toc.html -->
<leo_file xmlns:leo="https://leo-editor.github.io/leo-editor/namespaces/leo-python-editor/1.1" >
<leo_header file_format="2"/>
<globals/>
<preferences/>
<find_panel_settings/>
<vnodes>
<v t="ekr.20180311131353.1" descendentVnodeUnknownAttributes="7d710058010000003071017d7102580b0000007374725f6c656f5f706f7371035804000000332c3131710473732e"><vh>test.leo: semi-official tests</vh></v>
<v t="ekr.20230622114548.1"><vh>@settings</vh>
<v t="ekr.20231231164138.1"><vh>@data unl-path-prefixes</vh></v>
<v t="ekr.20230623064935.1"><vh>@data history-list</vh></v>
<v t="ekr.20230622114556.1"><vh>@button copy-test</vh></v>
<v t="ekr.20180311210803.1"><vh>Latex buttons</vh>
<v t="ekr.20180311195905.1"><vh>@@button Create LaTeX</vh></v>
<v t="ekr.20180311200239.1"><vh>@@clean c:\test\latex_test.tex</vh>
<v t="ekr.20180311200312.1"><vh>level 1</vh>
<v t="ekr.20180311200318.1"><vh>level 2</vh></v>
</v>
</v>
</v>
</v>
<v t="ekr.20231231164138.1"></v>
<v t="ekr.20241206101317.1" descendentVnodeUnknownAttributes="7d71002858010000003071017d71025808000000616e6e6f7461746571037d71042858080000007072696f7269747971054d0f27580a000000707269736574646174657106580a000000323032342d30342d3130710775735805000000302e312e3271087d7109580b0000005f5f626f6f6b6d61726b73710a7d710b580700000069735f64757065710c4930300a73735809000000302e312e322e302e30710d7d710e580b0000005f5f626f6f6b6d61726b73710f7d7110580700000069735f6475706571114930300a73735805000000302e322e3071127d7113580b0000005f5f626f6f6b6d61726b7371147d7115580700000069735f6475706571164930300a73735805000000302e322e3171177d7118580b0000005f5f626f6f6b6d61726b7371197d711a580700000069735f64757065711b4930300a7373752e"><vh>Projects</vh>
<v t="ekr.20241206101317.2"><vh>--- demo.py stuff</vh>
<v t="ekr.20241206101317.3"><vh>@@@button Demo @key=Ctrl-9</vh>
<v t="ekr.20241206101317.4"><vh>&lt;&lt; imports &gt;&gt;</vh></v>
<v t="ekr.20241206101317.5"><vh>&lt;&lt; class myDemo &gt;&gt;</vh>
<v t="ekr.20241206101317.6"><vh>setup</vh></v>
<v t="ekr.20241206101317.7"><vh>setup_script</vh></v>
<v t="ekr.20241206101317.8"><vh>teardown</vh></v>
<v t="ekr.20241206101317.9"><vh>teardown_script</vh></v>
</v>
<v t="ekr.20241206101317.10"><vh>demo-script</vh>
<v t="ekr.20241206101317.11"><vh>First</vh></v>
<v t="ekr.20241206101317.12"><vh>Full featured outliner</vh></v>
<v t="ekr.20241206101317.13"><vh>Leo's panes</vh></v>
<v t="ekr.20241206101317.14"><vh>Leo creates scripts from outlines</vh></v>
<v t="ekr.20241206101317.15"><vh>Clones and cff</vh></v>
<v t="ekr.20241206101317.16"><vh>Last</vh></v>
</v>
</v>
<v t="ekr.20241206101317.17"><vh>Demo Area</vh></v>
<v t="ekr.20241206101317.18"><vh>@@button IntroSlides @key=Ctrl-9</vh>
<v t="ekr.20241206101317.19"><vh>&lt;&lt; imports &gt;&gt;</vh></v>
<v t="ekr.20241206101317.20"><vh>&lt;&lt; class IntroSlides &gt;&gt;</vh>
<v t="ekr.20241206101317.21"><vh>setup</vh></v>
<v t="ekr.20241206101317.22"><vh>setup_script</vh></v>
<v t="ekr.20241206101317.23"><vh>teardown</vh></v>
<v t="ekr.20241206101317.24"><vh>teardown_script</vh></v>
</v>
<v t="ekr.20241206101317.25"><vh>&lt;&lt; main &gt;&gt;</vh></v>
<v t="ekr.20241206101317.26"><vh>Leo's Main Window</vh></v>
<v t="ekr.20241206101317.27"><vh>intro-slides-script</vh>
<v t="ekr.20241206101317.28"><vh>Slide 1: Leo's main window</vh></v>
</v>
</v>
</v>
<v t="ekr.20241206101317.29" descendentVnodeUnknownAttributes="7d7100285803000000302e3271017d7102580b0000005f5f626f6f6b6d61726b7371037d7104580700000069735f6475706571054930300a73735807000000302e322e302e3071067d7107580b0000005f5f626f6f6b6d61726b7371087d7109580700000069735f64757065710a4930300a7373752e"><vh>--- working graphics stuff</vh>
<v t="ekr.20241206101317.30"><vh>run turtledemo/penrose.py</vh></v>
<v t="ekr.20241206101317.31"><vh>mathjax rendering (VR3 only)</vh>
<v t="ekr.20241206101317.32"><vh>markdown example from .ipynb file</vh></v>
<v t="ekr.20241206101317.33"><vh>mathjax-test2.html</vh></v>
<v t="ekr.20241206101317.34"><vh>VR3: test TeX markup (works)</vh></v>
</v>
<v t="ekr.20241206101317.35" descendentVnodeUnknownAttributes="7d71002858010000003071017d7102580b0000005f5f626f6f6b6d61726b7371037d7104580700000069735f6475706571054930300a73735805000000302e302e3071067d7107580b0000005f5f626f6f6b6d61726b7371087d7109580700000069735f64757065710a4930300a7373752e"><vh>working VR examples</vh>
<v t="ekr.20241206101317.36" descendentVnodeUnknownAttributes="7d71005803000000302e3071017d7102580b0000005f5f626f6f6b6d61726b7371037d7104580700000069735f6475706571054930300a7373732e"><vh>at-html tests</vh>
<v t="ekr.20241206101317.37" descendentVnodeUnknownAttributes="7d710058010000003071017d7102580b0000005f5f626f6f6b6d61726b7371037d7104580700000069735f6475706571054930300a7373732e"><vh>@html sphinx Link</vh></v>
<v t="ekr.20241206101317.38"><vh>@html display Leo tree (cool)</vh></v>
<v t="ekr.20241206101317.39"><vh>@html Leo's home page</vh></v>
</v>
<v t="ekr.20241206101317.40"><vh>at-image tests</vh>
<v t="ekr.20241206101317.41"><vh>@image LeoSplash (works, but very large)</vh></v>
<v t="ekr.20241206101317.42"><vh>@path C:\Repos\leo-editor\leo\Icons</vh>
<v t="ekr.20241206101317.43"><vh>@image LeoSplash (test: using @path)</vh></v>
</v>
</v>
<v t="ekr.20241206101317.44"><vh>at-pyplot tests</vh>
<v t="ekr.20241206101317.45"><vh>@pyplot barchar_demo</vh></v>
<v t="ekr.20241206101317.46"><vh>@pyplot basic_example 1</vh></v>
<v t="ekr.20241206101317.47"><vh>@pyplot basic_example 2</vh></v>
<v t="ekr.20241206101317.48"><vh>@pyplot matplotlib animate_decay</vh>
<v t="ekr.20241206101317.49"><vh>data_gen</vh></v>
<v t="ekr.20241206101317.50"><vh>init</vh></v>
<v t="ekr.20241206101317.51"><vh>run</vh></v>
</v>
<v t="ekr.20241206101317.52"><vh>@pyplot two graphs in same window</vh></v>
</v>
<v t="ekr.20241206101317.53"><vh>at-svg tests</vh>
<v t="ekr.20241206101317.54"><vh>@svg bubbles sources</vh></v>
<v t="ekr.20241206101317.55"><vh>@svg bubbles.svg</vh></v>
</v>
<v t="ekr.20241206101317.56"><vh>docstring test</vh></v>
<v t="ekr.20241206101317.57"><vh>markdown tests</vh>
<v t="ekr.20241206101317.58"><vh>markdown test (including url)</vh></v>
<v t="ekr.20241206101317.59"><vh>markdown test (math)</vh></v>
<v t="ekr.20241206101317.60"><vh>markdown test (link)</vh></v>
</v>
<v t="ekr.20241206101317.61"><vh>rst tests</vh>
<v t="ekr.20241206101317.62"><vh>rst test (including url)</vh></v>
<v t="ekr.20241206101317.63"><vh>rst example</vh>
<v t="ekr.20241206101317.64"><vh>python</vh></v>
</v>
<v t="ekr.20241206101317.65"><vh>python code block in rst</vh></v>
</v>
<v t="ekr.20241206101317.66"><vh>URLs tests</vh>
<v t="ekr.20241206101317.67"><vh>@url Leo's home page (md)</vh></v>
<v t="ekr.20241206101317.68"><vh>@url Leo's home page (rST)</vh></v>
<v t="ekr.20241206101317.69"><vh>@url mail (rst)</vh></v>
<v t="ekr.20241206101317.70"><vh>@url weather (md)</vh></v>
</v>
</v>
</v>
<v t="ekr.20241206101317.71" descendentVnodeUnknownAttributes="7d7100285803000000302e3071017d7102580b0000005f5f626f6f6b6d61726b7371037d7104580700000069735f6475706571054930300a73735803000000302e3171067d7107580b0000005f5f626f6f6b6d61726b7371087d7109580700000069735f64757065710a4930300a7373752e"><vh>--- broken graphics stuff</vh>
<v t="ekr.20241206101317.72" descendentVnodeUnknownAttributes="7d710058010000003071017d7102580b0000005f5f626f6f6b6d61726b7371037d7104580700000069735f6475706571054930300a7373732e"><vh>barchar_demo (Matplot plugin)</vh></v>
<v t="ekr.20241206101317.73" descendentVnodeUnknownAttributes="7d710058010000003071017d7102580b0000005f5f626f6f6b6d61726b7371037d7104580700000069735f6475706571054930300a7373732e"><vh>md pictures</vh></v>
<v t="ekr.20241206101317.74"><vh>Render latex in VR (not ready for Python 3)</vh></v>
</v>
<v t="ekr.20241206101317.86"><vh>--- jinja</vh>
<v t="ekr.20241206101317.87"><vh>@jinja</vh>
<v t="ekr.20241206101317.88"><vh>@jinja template</vh></v>
<v t="ekr.20241206101317.89"><vh>@jinja inputs</vh>
<v t="ekr.20241206101317.90"><vh>@jinja variable name</vh></v>
</v>
</v>
<v t="ekr.20241206101317.91"><vh>update_jinja</vh></v>
<v t="ekr.20241206101317.92"><vh>jinja2</vh></v>
</v>
</v>
<v t="ekr.20241206101345.1" descendentVnodeUnknownAttributes="7d710058010000003071017d71025808000000616e6e6f7461746571037d71042858080000007072696f7269747971054d0f27580a000000707269736574646174657106580a000000323032342d30342d313071077573732e"><vh>Tests</vh>
<v t="ekr.20230622112641.1"><vh>Tests of UNL (do not delete!)</vh>
<v t="ekr.20230622112649.1"><vh>Error messages (copy to log)</vh></v>
<v t="ekr.20230625084119.1"><vh>Legacy UNLs: in this file</vh></v>
<v t="ekr.20230628090119.1"><vh>Legacy UNLs: other files</vh></v>
<v t="ekr.20230623141357.1"><vh>URLs and UNLs: </vh></v>
<v t="ekr.20231231043342.1"><vh>New UNLs with empty file parts</vh></v>
<v t="ekr.20230627150454.1"><vh>New UNLs with short file parts</vh></v>
<v t="ekr.20240226115140.1"><vh>New UNLs with relative file parts</vh></v>
<v t="ekr.20230707065449.1"><vh>New UNLS with long(absolute) file parts</vh></v>
</v>
<v t="ekr.20241206101345.2"><vh>Syntax coloring tests</vh>
<v t="ekr.20241206101345.3"><vh>rust colorizing test</vh></v>
<v t="ekr.20241206101345.4"><vh>nim syntax coloring test</vh></v>
<v t="ekr.20241206101345.5"><vh>csharp colorizer test</vh></v>
<v t="ekr.20241206101345.6"><vh>wikiview test</vh></v>
<v t="ekr.20241206101345.7"><vh>--- coloring tests</vh>
<v t="ekr.20241206101345.475"><vh>test: python</vh></v>
<v t="ekr.20241206101345.9"><vh>test: md</vh></v>
<v t="ekr.20241206101345.471"><vh>test: jupytext: both</vh></v>
<v t="ekr.20241206101345.11"><vh>test: jupytext: md</vh></v>
<v t="ekr.20241206101345.12"><vh>test: jupytext: python</vh></v>
<v t="ekr.20241206101345.13"><vh>Test of @others</vh></v>
<v t="ekr.20241206101345.472"><vh>test: language rest/python</vh></v>
<v t="ekr.20241206101345.15"><vh>test: at-language md/python</vh></v>
<v t="ekr.20241206101345.16"><vh>test: @killcolor</vh></v>
<v t="ekr.20241206101345.17"><vh>test: @nocolor &amp; @color</vh></v>
<v t="ekr.20241206101345.18"><vh>test: python &amp; md</vh></v>
<v t="ekr.20241206101345.19"><vh>cweb</vh></v>
<v t="ekr.20241206101345.20"><vh>elisp (lisp)</vh></v>
</v>
<v t="ekr.20241206101345.21"><vh>--- jupytext stuff </vh>
<v t="ekr.20241206101345.22"><vh>--- jupytext sources (study only)</vh>
<v t="ekr.20241206101345.23"><vh> Python3.12/Lib/site-packages/jupytext</vh>
<v t="ekr.20241206101345.24"><vh>__init__.py (see __all__)</vh></v>
<v t="ekr.20241206101345.25"><vh>__main__.py</vh></v>
<v t="ekr.20241206101345.26"><vh>cell_metadata.py</vh>
<v t="ekr.20241206101345.27"><vh>class RLogicalValueError</vh></v>
<v t="ekr.20241206101345.28"><vh>class RMarkdownOptionParsingError</vh></v>
<v t="ekr.20241206101345.29"><vh>function: _py_logical_values</vh></v>
<v t="ekr.20241206101345.30"><vh>function: metadata_to_rmd_options</vh></v>
<v t="ekr.20241206101345.31"><vh>function: update_metadata_from_rmd_options</vh></v>
<v t="ekr.20241206101345.32"><vh>class ParsingContext</vh>
<v t="ekr.20241206101345.33"><vh>ParsingContext.__init__</vh></v>
<v t="ekr.20241206101345.34"><vh>ParsingContext.in_global_expression</vh></v>
<v t="ekr.20241206101345.35"><vh>ParsingContext.count_special_chars</vh></v>
</v>
<v t="ekr.20241206101345.36"><vh>function: parse_rmd_options</vh></v>
<v t="ekr.20241206101345.37"><vh>function: rmd_options_to_metadata</vh></v>
<v t="ekr.20241206101345.38"><vh>function: try_eval_metadata</vh></v>
<v t="ekr.20241206101345.39"><vh>function: is_active</vh></v>
<v t="ekr.20241206101345.40"><vh>function: metadata_to_double_percent_options</vh></v>
<v t="ekr.20241206101345.41"><vh>function: incorrectly_encoded_metadata</vh></v>
<v t="ekr.20241206101345.42"><vh>function: is_identifier</vh></v>
<v t="ekr.20241206101345.43"><vh>function: is_valid_metadata_key</vh></v>
<v t="ekr.20241206101345.44"><vh>function: is_jupyter_language</vh></v>
<v t="ekr.20241206101345.45"><vh>function: parse_key_equal_value</vh></v>
<v t="ekr.20241206101345.46"><vh>function: relax_json_loads</vh></v>
<v t="ekr.20241206101345.47"><vh>function: is_json_metadata</vh></v>
<v t="ekr.20241206101345.48"><vh>function: text_to_metadata</vh></v>
<v t="ekr.20241206101345.49"><vh>function: metadata_to_text</vh></v>
</v>
<v t="ekr.20241206101345.50"><vh>cell_reader.py</vh>
<v t="ekr.20241206101345.51"><vh>function: uncomment</vh></v>
<v t="ekr.20241206101345.52"><vh>function: paragraph_is_fully_commented</vh></v>
<v t="ekr.20241206101345.53"><vh>function: next_code_is_indented</vh></v>
<v t="ekr.20241206101345.54"><vh>function: count_lines_to_next_cell</vh></v>
<v t="ekr.20241206101345.55"><vh>function: last_two_lines_blank</vh></v>
<v t="ekr.20241206101345.56"><vh>class BaseCellReader</vh>
<v t="ekr.20241206101345.57"><vh>BaseCellReader.__init__</vh></v>
<v t="ekr.20241206101345.58"><vh>BaseCellReader.read</vh></v>
<v t="ekr.20241206101345.59"><vh>BaseCellReader.metadata_and_language_from_option_line</vh></v>
<v t="ekr.20241206101345.60"><vh>BaseCellReader.options_to_metadata</vh></v>
<v t="ekr.20241206101345.61"><vh>BaseCellReader.find_cell_end</vh></v>
<v t="ekr.20241206101345.62"><vh>BaseCellReader.find_cell_content</vh></v>
<v t="ekr.20241206101345.63"><vh>BaseCellReader.uncomment_code_and_magics</vh></v>
<v t="ekr.20241206101345.64"><vh>BaseCellReader.extract_content</vh></v>
</v>
<v t="ekr.20241206101345.65"><vh>class MarkdownCellReader</vh>
<v t="ekr.20241206101345.66"><vh>MarkdownCellReader.__init__</vh></v>
<v t="ekr.20241206101345.67"><vh>MarkdownCellReader.metadata_and_language_from_option_line</vh></v>
<v t="ekr.20241206101345.68"><vh>MarkdownCellReader.options_to_metadata</vh></v>
<v t="ekr.20241206101345.69"><vh>MarkdownCellReader.find_cell_end</vh></v>
<v t="ekr.20241206101345.70"><vh>MarkdownCellReader.uncomment_code_and_magics</vh></v>
</v>
<v t="ekr.20241206101345.71"><vh>class RMarkdownCellReader</vh>
<v t="ekr.20241206101345.72"><vh>RMarkdownCellReader.options_to_metadata</vh></v>
<v t="ekr.20241206101345.73"><vh>RMarkdownCellReader.uncomment_code_and_magics</vh></v>
</v>
<v t="ekr.20241206101345.74"><vh>class ScriptCellReader</vh>
<v t="ekr.20241206101345.75"><vh>ScriptCellReader.uncomment_code_and_magics</vh></v>
</v>
<v t="ekr.20241206101345.76"><vh>class RScriptCellReader</vh>
<v t="ekr.20241206101345.77"><vh>RScriptCellReader.options_to_metadata</vh></v>
<v t="ekr.20241206101345.78"><vh>RScriptCellReader.find_cell_end</vh></v>
</v>
<v t="ekr.20241206101345.79"><vh>class LightScriptCellReader</vh>
<v t="ekr.20241206101345.80"><vh>LightScriptCellReader.__init__</vh></v>
<v t="ekr.20241206101345.81"><vh>LightScriptCellReader.metadata_and_language_from_option_line</vh></v>
<v t="ekr.20241206101345.82"><vh>LightScriptCellReader.options_to_metadata</vh></v>
<v t="ekr.20241206101345.83"><vh>LightScriptCellReader.find_cell_end</vh></v>
<v t="ekr.20241206101345.84"><vh>LightScriptCellReader.find_region_end</vh></v>
</v>
<v t="ekr.20241206101345.85"><vh>class DoublePercentScriptCellReader</vh>
<v t="ekr.20241206101345.86"><vh>DoublePercentScriptCellReader.__init__</vh></v>
<v t="ekr.20241206101345.87"><vh>DoublePercentScriptCellReader.metadata_and_language_from_option_line</vh></v>
<v t="ekr.20241206101345.88"><vh>DoublePercentScriptCellReader.find_cell_content</vh></v>
<v t="ekr.20241206101345.89"><vh>DoublePercentScriptCellReader.find_cell_end</vh></v>
</v>
<v t="ekr.20241206101345.90"><vh>class HydrogenCellReader</vh></v>
<v t="ekr.20241206101345.91"><vh>class SphinxGalleryScriptCellReader</vh>
<v t="ekr.20241206101345.92"><vh>SphinxGalleryScriptCellReader.__init__</vh></v>
<v t="ekr.20241206101345.93"><vh>SphinxGalleryScriptCellReader.start_of_new_markdown_cell</vh></v>
<v t="ekr.20241206101345.94"><vh>SphinxGalleryScriptCellReader.metadata_and_language_from_option_line</vh></v>
<v t="ekr.20241206101345.95"><vh>SphinxGalleryScriptCellReader.find_cell_end</vh></v>
<v t="ekr.20241206101345.96"><vh>SphinxGalleryScriptCellReader.find_cell_content</vh></v>
</v>
</v>
<v t="ekr.20241206101345.97"><vh>cell_to_text.py</vh>
<v t="ekr.20241206101345.98"><vh>function: cell_source</vh></v>
<v t="ekr.20241206101345.99"><vh>function: three_backticks_or_more</vh></v>
<v t="ekr.20241206101345.100"><vh>class BaseCellExporter</vh>
<v t="ekr.20241206101345.101"><vh>BaseCellExporter.__init__</vh></v>
<v t="ekr.20241206101345.102"><vh>BaseCellExporter.is_code</vh></v>
<v t="ekr.20241206101345.103"><vh>BaseCellExporter.use_triple_quotes</vh></v>
<v t="ekr.20241206101345.104"><vh>BaseCellExporter.cell_to_text</vh></v>
<v t="ekr.20241206101345.105"><vh>BaseCellExporter.markdown_to_text</vh></v>
<v t="ekr.20241206101345.106"><vh>BaseCellExporter.code_to_text</vh></v>
<v t="ekr.20241206101345.107"><vh>BaseCellExporter.remove_eoc_marker</vh></v>
</v>
<v t="ekr.20241206101345.108"><vh>class MarkdownCellExporter</vh>
<v t="ekr.20241206101345.109"><vh>MarkdownCellExporter.__init__</vh></v>
<v t="ekr.20241206101345.110"><vh>MarkdownCellExporter.html_comment</vh></v>
<v t="ekr.20241206101345.111"><vh>MarkdownCellExporter.cell_to_text</vh></v>
<v t="ekr.20241206101345.112"><vh>MarkdownCellExporter.code_to_text</vh></v>
</v>
<v t="ekr.20241206101345.113"><vh>class RMarkdownCellExporter</vh>
<v t="ekr.20241206101345.114"><vh>RMarkdownCellExporter.__init__</vh></v>
<v t="ekr.20241206101345.115"><vh>RMarkdownCellExporter.code_to_text</vh></v>
</v>
<v t="ekr.20241206101345.116"><vh>function: endofcell_marker</vh></v>
<v t="ekr.20241206101345.117"><vh>class LightScriptCellExporter</vh>
<v t="ekr.20241206101345.118"><vh>LightScriptCellExporter.__init__</vh></v>
<v t="ekr.20241206101345.119"><vh>LightScriptCellExporter.is_code</vh></v>
<v t="ekr.20241206101345.120"><vh>LightScriptCellExporter.code_to_text</vh></v>
<v t="ekr.20241206101345.121"><vh>LightScriptCellExporter.explicit_start_marker</vh></v>
<v t="ekr.20241206101345.122"><vh>LightScriptCellExporter.remove_eoc_marker</vh></v>
</v>
<v t="ekr.20241206101345.123"><vh>class BareScriptCellExporter</vh></v>
<v t="ekr.20241206101345.124"><vh>class RScriptCellExporter</vh>
<v t="ekr.20241206101345.125"><vh>RScriptCellExporter.__init__</vh></v>
<v t="ekr.20241206101345.126"><vh>RScriptCellExporter.code_to_text</vh></v>
</v>
<v t="ekr.20241206101345.127"><vh>class DoublePercentCellExporter</vh>
<v t="ekr.20241206101345.128"><vh>DoublePercentCellExporter.__init__</vh></v>
<v t="ekr.20241206101345.129"><vh>DoublePercentCellExporter.cell_to_text</vh></v>
</v>
<v t="ekr.20241206101345.130"><vh>class HydrogenCellExporter</vh></v>
<v t="ekr.20241206101345.131"><vh>class SphinxGalleryCellExporter</vh>
<v t="ekr.20241206101345.132"><vh>SphinxGalleryCellExporter.__init__</vh></v>
<v t="ekr.20241206101345.133"><vh>SphinxGalleryCellExporter.cell_to_text</vh></v>
</v>
</v>
<v t="ekr.20241206101345.378"><vh>cli.py</vh>
<v t="ekr.20241206101345.379"><vh>function: system</vh></v>
<v t="ekr.20241206101345.380"><vh>function: tool_version</vh></v>
<v t="ekr.20241206101345.381"><vh>function: str2bool</vh></v>
<v t="ekr.20241206101345.382"><vh>function: parse_jupytext_args</vh></v>
<v t="ekr.20241206101345.401"><vh>function: jupytext</vh></v>
<v t="ekr.20241206101345.402"><vh>function: jupytext_single_file</vh></v>
<v t="ekr.20241206101345.385"><vh>function: notebooks_in_git_index</vh></v>
<v t="ekr.20241206101345.386"><vh>function: is_untracked</vh></v>
<v t="ekr.20241206101345.387"><vh>function: print_paired_paths</vh></v>
<v t="ekr.20241206101345.388"><vh>function: set_format_options</vh></v>
<v t="ekr.20241206101345.389"><vh>function: set_prefix_and_suffix</vh></v>
<v t="ekr.20241206101345.390"><vh>class NotAPairedNotebook</vh></v>
<v t="ekr.20241206101345.391"><vh>class InconsistentVersions</vh></v>
<v t="ekr.20241206101345.392"><vh>function: file_in_git_index</vh></v>
<v t="ekr.20241206101345.393"><vh>function: git_timestamp</vh></v>
<v t="ekr.20241206101345.394"><vh>function: get_timestamp</vh></v>
<v t="ekr.20241206101345.395"><vh>function: load_paired_notebook</vh></v>
<v t="ekr.20241206101345.396"><vh>function: exec_command</vh></v>
<v t="ekr.20241206101345.397"><vh>function: pipe_notebook</vh></v>
<v t="ekr.20241206101345.398"><vh>function: execution_counts_are_in_order</vh></v>
<v t="ekr.20241206101345.399"><vh>function: code_cells_have_changed</vh></v>
</v>
<v t="ekr.20241206101345.156"><vh>combine.py</vh>
<v t="ekr.20241206101345.157"><vh>function: black_invariant</vh></v>
<v t="ekr.20241206101345.158"><vh>function: same_content</vh></v>
<v t="ekr.20241206101345.159"><vh>function: combine_inputs_with_outputs</vh></v>
<v t="ekr.20241206101345.160"><vh>function: map_outputs_to_inputs</vh></v>
</v>
<v t="ekr.20241206101345.161"><vh>compare.py</vh>
<v t="ekr.20241206101345.162"><vh>function: _multilines</vh></v>
<v t="ekr.20241206101345.163"><vh>function: compare</vh></v>
<v t="ekr.20241206101345.164"><vh>function: filtered_cell</vh></v>
<v t="ekr.20241206101345.165"><vh>function: filtered_notebook_metadata</vh></v>
<v t="ekr.20241206101345.166"><vh>class NotebookDifference</vh></v>
<v t="ekr.20241206101345.167"><vh>function: same_content</vh></v>
<v t="ekr.20241206101345.168"><vh>function: compare_notebooks</vh></v>
<v t="ekr.20241206101345.169"><vh>function: compare_cells</vh></v>
<v t="ekr.20241206101345.170"><vh>function: test_round_trip_conversion</vh></v>
<v t="ekr.20241206101345.171"><vh>function: create_mirror_file_if_missing</vh></v>
<v t="ekr.20241206101345.172"><vh>function: assert_conversion_same_as_mirror</vh></v>
<v t="ekr.20241206101345.173"><vh>function: notebook_model</vh></v>
</v>
<v t="ekr.20241206101345.174"><vh>config.py</vh>
<v t="ekr.20241206101345.175"><vh>class JupytextConfigurationError</vh></v>
<v t="ekr.20241206101345.176"><vh>class JupytextConfiguration</vh>
<v t="ekr.20241206101345.177"><vh>JupytextConfiguration.set_default_format_options</vh></v>
<v t="ekr.20241206101345.178"><vh>JupytextConfiguration.default_formats</vh></v>
<v t="ekr.20241206101345.179"><vh>JupytextConfiguration.__eq__</vh></v>
</v>
<v t="ekr.20241206101345.180"><vh>function: preferred_format</vh></v>
<v t="ekr.20241206101345.181"><vh>function: global_jupytext_configuration_directories</vh></v>
<v t="ekr.20241206101345.182"><vh>function: find_global_jupytext_configuration_file</vh></v>
<v t="ekr.20241206101345.183"><vh>function: find_jupytext_configuration_file</vh></v>
<v t="ekr.20241206101345.184"><vh>function: parse_jupytext_configuration_file</vh></v>
<v t="ekr.20241206101345.185"><vh>function: load_jupytext_configuration_file</vh></v>
<v t="ekr.20241206101345.186"><vh>function: load_jupytext_config</vh></v>
<v t="ekr.20241206101345.187"><vh>function: validate_jupytext_configuration_file</vh></v>
<v t="ekr.20241206101345.188"><vh>function: notebook_formats</vh></v>
</v>
<v t="ekr.20241206101345.189"><vh>contentsmanager.py</vh>
<v t="ekr.20241206101345.190"><vh>function: build_jupytext_contents_manager_class</vh>
<v t="ekr.20241206101345.191"><vh>class JupytextContentsManager</vh>
<v t="ekr.20241206101345.192"><vh>JupytextContentsManager.__init__</vh></v>
<v t="ekr.20241206101345.193"><vh>JupytextContentsManager.all_nb_extensions</vh></v>
<v t="ekr.20241206101345.194"><vh>JupytextContentsManager.drop_paired_notebook</vh></v>
<v t="ekr.20241206101345.195"><vh>JupytextContentsManager.update_paired_notebooks</vh></v>
<v t="ekr.20241206101345.196"><vh>JupytextContentsManager.create_prefix_dir</vh></v>
<v t="ekr.20241206101345.197"><vh>JupytextContentsManager.save</vh></v>
<v t="ekr.20241206101345.198"><vh>JupytextContentsManager.get</vh></v>
</v>
</v>
<v t="ekr.20241206101345.199"><vh>function: new_untitled</vh></v>
<v t="ekr.20241206101345.200"><vh>function: increment_notebook_filename</vh></v>
<v t="ekr.20241206101345.201"><vh>function: trust_notebook</vh></v>
<v t="ekr.20241206101345.202"><vh>function: rename_file</vh></v>
<v t="ekr.20241206101345.203"><vh>function: get_parent_dir</vh></v>
<v t="ekr.20241206101345.204"><vh>function: get_config_file</vh></v>
<v t="ekr.20241206101345.205"><vh>function: load_config_file</vh></v>
<v t="ekr.20241206101345.206"><vh>function: get_config</vh></v>
</v>
<v t="ekr.20241206101345.207"><vh>doxygen.py</vh>
<v t="ekr.20241206101345.208"><vh>function: markdown_to_doxygen</vh></v>
<v t="ekr.20241206101345.209"><vh>function: doxygen_to_markdown</vh></v>
</v>
<v t="ekr.20241206101345.210"><vh>formats.py</vh>
<v t="ekr.20241206101345.211"><vh>class JupytextFormatError</vh></v>
<v t="ekr.20241206101345.212"><vh>class NotebookFormatDescription</vh>
<v t="ekr.20241206101345.213"><vh>NotebookFormatDescription.__init__</vh></v>
</v>
<v t="ekr.20241206101345.214"><vh>function: get_format_implementation</vh></v>
<v t="ekr.20241206101345.215"><vh>function: read_metadata</vh></v>
<v t="ekr.20241206101345.216"><vh>function: read_format_from_metadata</vh></v>
<v t="ekr.20241206101345.217"><vh>function: guess_format</vh></v>
<v t="ekr.20241206101345.218"><vh>function: divine_format</vh></v>
<v t="ekr.20241206101345.219"><vh>function: check_file_version</vh></v>
<v t="ekr.20241206101345.220"><vh>function: format_name_for_ext</vh></v>
<v t="ekr.20241206101345.221"><vh>function: identical_format_path</vh></v>
<v t="ekr.20241206101345.222"><vh>function: update_jupytext_formats_metadata</vh></v>
<v t="ekr.20241206101345.223"><vh>function: rearrange_jupytext_metadata</vh></v>
<v t="ekr.20241206101345.224"><vh>function: long_form_one_format</vh></v>
<v t="ekr.20241206101345.225"><vh>function: long_form_multiple_formats</vh></v>
<v t="ekr.20241206101345.226"><vh>function: short_form_one_format</vh></v>
<v t="ekr.20241206101345.227"><vh>function: short_form_multiple_formats</vh></v>
<v t="ekr.20241206101345.228"><vh>function: validate_one_format</vh></v>
<v t="ekr.20241206101345.229"><vh>function: auto_ext_from_metadata</vh></v>
<v t="ekr.20241206101345.230"><vh>function: check_auto_ext</vh></v>
<v t="ekr.20241206101345.231"><vh>function: formats_with_support_for_cell_metadata</vh></v>
</v>
<v t="ekr.20241206101345.232"><vh>header.py</vh>
<v t="ekr.20241206101345.233"><vh>function: insert_or_test_version_number</vh></v>
<v t="ekr.20241206101345.234"><vh>function: uncomment_line</vh></v>
<v t="ekr.20241206101345.235"><vh>function: encoding_and_executable</vh></v>
<v t="ekr.20241206101345.236"><vh>function: insert_jupytext_info_and_filter_metadata</vh></v>
<v t="ekr.20241206101345.237"><vh>function: metadata_and_cell_to_header</vh></v>
<v t="ekr.20241206101345.238"><vh>function: recursive_update</vh></v>
<v t="ekr.20241206101345.239"><vh>function: header_to_metadata_and_cell</vh></v>
</v>
<v t="ekr.20241206101345.407"><vh>jupytext.py (main entry points)</vh>
<v t="ekr.20241206101345.408"><vh>class NotSupportedNBFormatVersion(NotImplementedError)</vh></v>
<v t="ekr.20241206101345.409"><vh>class TextNotebookConverter(NotebookReader, NotebookWriter)</vh>
<v t="ekr.20241206101345.410"><vh>TextNotebookConverter.__init__</vh></v>
<v t="ekr.20241206101345.411"><vh>TextNotebookConverter.update_fmt_with_notebook_options</vh></v>
<v t="ekr.20241206101345.412"><vh>TextNotebookConverter.reads</vh></v>
<v t="ekr.20241206101345.413"><vh>TextNotebookConverter.filter_notebook</vh></v>
<v t="ekr.20241206101345.414"><vh>TextNotebookConverter.writes</vh></v>
</v>
<v t="ekr.20241206101345.415"><vh>function: reads (entry point: jupytext.py)</vh></v>
<v t="ekr.20241206101345.416"><vh>function: read (entry point: jupytext.py)</vh></v>
<v t="ekr.20241206101345.417"><vh>function: writes (entry point: jupytext.py)</vh></v>
<v t="ekr.20241206101345.418"><vh>function: drop_text_representation_metadata (jupytext.py)</vh></v>
<v t="ekr.20241206101345.419"><vh>function: write (entry_point: jupytext.py)</vh></v>
<v t="ekr.20241206101345.420"><vh>function: create_prefix_dir</vh></v>
<v t="ekr.20241206101345.421"><vh>function: _warn_on_unsupported_keys</vh></v>
</v>
<v t="ekr.20241206101345.255"><vh>kernels.py</vh>
<v t="ekr.20241206101345.256"><vh>function: set_kernelspec_from_language</vh></v>
<v t="ekr.20241206101345.257"><vh>function: kernelspec_from_language</vh></v>
</v>
<v t="ekr.20241206101345.258"><vh>languages.py</vh>
<v t="ekr.20241206101345.259"><vh>function: default_language_from_metadata_and_ext</vh></v>
<v t="ekr.20241206101345.260"><vh>function: usual_language_name</vh></v>
<v t="ekr.20241206101345.261"><vh>function: same_language</vh></v>
<v t="ekr.20241206101345.262"><vh>function: set_main_and_cell_language</vh></v>
<v t="ekr.20241206101345.263"><vh>function: cell_language</vh></v>
<v t="ekr.20241206101345.264"><vh>function: comment_lines</vh></v>
</v>
<v t="ekr.20241206101345.265"><vh>magics.py</vh>
<v t="ekr.20241206101345.266"><vh>function: get_comment</vh></v>
<v t="ekr.20241206101345.267"><vh>function: is_magic</vh></v>
<v t="ekr.20241206101345.268"><vh>function: need_explicit_marker</vh></v>
<v t="ekr.20241206101345.269"><vh>function: comment_magic</vh></v>
<v t="ekr.20241206101345.270"><vh>function: unesc</vh></v>
<v t="ekr.20241206101345.271"><vh>function: uncomment_magic</vh></v>
<v t="ekr.20241206101345.272"><vh>function: is_escaped_code_start</vh></v>
<v t="ekr.20241206101345.273"><vh>function: escape_code_start</vh></v>
<v t="ekr.20241206101345.274"><vh>function: unescape_code_start</vh></v>
</v>
<v t="ekr.20241206101345.275"><vh>metadata_filter.py</vh>
<v t="ekr.20241206101345.276"><vh>function: metadata_filter_as_dict</vh></v>
<v t="ekr.20241206101345.277"><vh>function: metadata_filter_as_string</vh></v>
<v t="ekr.20241206101345.278"><vh>function: update_metadata_filters</vh></v>
<v t="ekr.20241206101345.279"><vh>function: filter_metadata</vh></v>
<v t="ekr.20241206101345.280"><vh>function: second_level</vh></v>
<v t="ekr.20241206101345.281"><vh>function: suppress_unsupported_keys</vh></v>
<v t="ekr.20241206101345.282"><vh>function: subset_metadata</vh></v>
<v t="ekr.20241206101345.283"><vh>function: restore_filtered_metadata</vh></v>
</v>
<v t="ekr.20241206101345.284"><vh>myst.py</vh>
<v t="ekr.20241206101345.285"><vh>function: is_myst_available</vh></v>
<v t="ekr.20241206101345.286"><vh>function: raise_if_myst_is_not_available</vh></v>
<v t="ekr.20241206101345.287"><vh>function: myst_version</vh></v>
<v t="ekr.20241206101345.288"><vh>function: myst_extensions</vh></v>
<v t="ekr.20241206101345.289"><vh>function: get_parser</vh></v>
<v t="ekr.20241206101345.290"><vh>function: matches_mystnb</vh></v>
<v t="ekr.20241206101345.291"><vh>class CompactDumper</vh></v>
<v t="ekr.20241206101345.292"><vh>function: represent_list</vh></v>
<v t="ekr.20241206101345.293"><vh>function: represent_dict</vh></v>
<v t="ekr.20241206101345.294"><vh>function: dump_yaml_blocks</vh></v>
<v t="ekr.20241206101345.295"><vh>function: from_nbnode</vh></v>
<v t="ekr.20241206101345.296"><vh>class MystMetadataParsingError</vh></v>
<v t="ekr.20241206101345.297"><vh>function: strip_blank_lines</vh></v>
<v t="ekr.20241206101345.298"><vh>function: read_fenced_cell</vh></v>
<v t="ekr.20241206101345.299"><vh>function: parse_directive_options</vh></v>
<v t="ekr.20241206101345.300"><vh>function: read_cell_metadata</vh></v>
<v t="ekr.20241206101345.301"><vh>function: myst_to_notebook</vh></v>
<v t="ekr.20241206101345.302"><vh>function: notebook_to_myst</vh></v>
</v>
<v t="ekr.20241206101345.303"><vh>paired_paths.py</vh>
<v t="ekr.20241206101345.304"><vh>class InconsistentPath</vh></v>
<v t="ekr.20241206101345.305"><vh>function: split</vh></v>
<v t="ekr.20241206101345.306"><vh>function: join</vh></v>
<v t="ekr.20241206101345.307"><vh>function: separator</vh></v>
<v t="ekr.20241206101345.308"><vh>function: base_path</vh></v>
<v t="ekr.20241206101345.309"><vh>function: full_path</vh></v>
<v t="ekr.20241206101345.310"><vh>function: find_base_path_and_format</vh></v>
<v t="ekr.20241206101345.311"><vh>function: paired_paths</vh></v>
</v>
<v t="ekr.20241206101345.312"><vh>pairs.py</vh>
<v t="ekr.20241206101345.313"><vh>class PairedFilesDiffer</vh></v>
<v t="ekr.20241206101345.314"><vh>function: write_pair</vh></v>
<v t="ekr.20241206101345.315"><vh>function: latest_inputs_and_outputs</vh></v>
<v t="ekr.20241206101345.316"><vh>function: read_pair</vh></v>
</v>
<v t="ekr.20241206101345.317"><vh>pandoc.py</vh>
<v t="ekr.20241206101345.318"><vh>class PandocError</vh></v>
<v t="ekr.20241206101345.319"><vh>function: pandoc</vh></v>
<v t="ekr.20241206101345.320"><vh>function: is_pandoc_available</vh></v>
<v t="ekr.20241206101345.321"><vh>function: raise_if_pandoc_is_not_available</vh></v>
<v t="ekr.20241206101345.322"><vh>function: pandoc_version</vh></v>
<v t="ekr.20241206101345.323"><vh>function: md_to_notebook</vh></v>
<v t="ekr.20241206101345.324"><vh>function: notebook_to_md</vh></v>
</v>
<v t="ekr.20241206101345.325"><vh>pep8.py</vh>
<v t="ekr.20241206101345.326"><vh>function: next_instruction_is_function_or_class</vh></v>
<v t="ekr.20241206101345.327"><vh>function: cell_ends_with_function_or_class</vh></v>
<v t="ekr.20241206101345.328"><vh>function: cell_ends_with_code</vh></v>
<v t="ekr.20241206101345.329"><vh>function: cell_has_code</vh></v>
<v t="ekr.20241206101345.330"><vh>function: pep8_lines_between_cells</vh></v>
</v>
<v t="ekr.20241206101345.331"><vh>quarto.py</vh>
<v t="ekr.20241206101345.332"><vh>class QuartoError</vh></v>
<v t="ekr.20241206101345.333"><vh>function: quarto</vh></v>
<v t="ekr.20241206101345.334"><vh>function: is_quarto_available</vh></v>
<v t="ekr.20241206101345.335"><vh>function: raise_if_quarto_is_not_available</vh></v>
<v t="ekr.20241206101345.336"><vh>function: quarto_version</vh></v>
<v t="ekr.20241206101345.337"><vh>function: qmd_to_notebook</vh></v>
<v t="ekr.20241206101345.338"><vh>function: notebook_to_qmd</vh></v>
</v>
<v t="ekr.20241206101345.339"><vh>reraise.py</vh>
<v t="ekr.20241206101345.340"><vh>function: reraise</vh></v>
</v>
<v t="ekr.20241206101345.341"><vh>stringparser.py</vh>
<v t="ekr.20241206101345.342"><vh>class StringParser</vh>
<v t="ekr.20241206101345.343"><vh>StringParser.__init__</vh></v>
<v t="ekr.20241206101345.344"><vh>StringParser.is_quoted</vh></v>
<v t="ekr.20241206101345.345"><vh>StringParser.read_line</vh></v>
</v>
</v>
<v t="ekr.20241206101345.346"><vh>version.py</vh></v>
</v>
<v t="ekr.20241206101345.347"><vh> Python3.12/Lib/site-packages/jupytext_config</vh>
<v t="ekr.20241206101345.348"><vh>__init__.py (empty)</vh></v>
<v t="ekr.20241206101345.349"><vh>__main__.py</vh></v>
<v t="ekr.20241206101345.350"><vh>jupytext_config.py</vh>
<v t="ekr.20241206101345.351"><vh>class SubCommand</vh>
<v t="ekr.20241206101345.352"><vh>SubCommand.__init__</vh></v>
<v t="ekr.20241206101345.353"><vh>SubCommand.main</vh></v>
</v>
<v t="ekr.20241206101345.354"><vh>class ListDefaultViewer</vh>
<v t="ekr.20241206101345.355"><vh>ListDefaultViewer.__init__</vh></v>
<v t="ekr.20241206101345.356"><vh>ListDefaultViewer.main</vh></v>
<v t="ekr.20241206101345.357"><vh>ListDefaultViewer.fill_parser</vh></v>
</v>
<v t="ekr.20241206101345.358"><vh>class SetDefaultViewer</vh>
<v t="ekr.20241206101345.359"><vh>SetDefaultViewer.__init__</vh></v>
<v t="ekr.20241206101345.360"><vh>SetDefaultViewer.main</vh></v>
<v t="ekr.20241206101345.361"><vh>SetDefaultViewer.fill_parser</vh></v>
</v>
<v t="ekr.20241206101345.362"><vh>class UnsetDefaultViewer</vh>
<v t="ekr.20241206101345.363"><vh>UnsetDefaultViewer.__init__</vh></v>
<v t="ekr.20241206101345.364"><vh>UnsetDefaultViewer.main</vh></v>
<v t="ekr.20241206101345.365"><vh>UnsetDefaultViewer.fill_parser</vh></v>
</v>
<v t="ekr.20241206101345.366"><vh>function: main</vh></v>
</v>
<v t="ekr.20241206101345.367"><vh>labconfig.py</vh>
<v t="ekr.20241206101345.368"><vh>class LabConfig</vh>
<v t="ekr.20241206101345.369"><vh>LabConfig.__init__</vh></v>
<v t="ekr.20241206101345.370"><vh>LabConfig.read</vh></v>
<v t="ekr.20241206101345.371"><vh>LabConfig.get_viewers</vh></v>
<v t="ekr.20241206101345.372"><vh>LabConfig.list_default_viewer</vh></v>
<v t="ekr.20241206101345.373"><vh>LabConfig.set_default_viewers</vh></v>
<v t="ekr.20241206101345.374"><vh>LabConfig.set_default_viewer</vh></v>
<v t="ekr.20241206101345.375"><vh>LabConfig.unset_default_viewers</vh></v>
<v t="ekr.20241206101345.376"><vh>LabConfig.unset_default_viewer</vh></v>
<v t="ekr.20241206101345.377"><vh>LabConfig.write</vh></v>
</v>
</v>
</v>
<v t="ekr.20241206101345.378"></v>
<v t="ekr.20241206101345.418"></v>
<v t="ekr.20241206101345.401"></v>
<v t="ekr.20241206101345.402"></v>
<v t="ekr.20241206101345.416"></v>
<v t="ekr.20241206101345.415"></v>
<v t="ekr.20241206101345.419"></v>
<v t="ekr.20241206101345.417"></v>
<v t="ekr.20241206101345.407"></v>
</v>
<v t="ekr.20241206101345.422"><vh>script: test-jupytext</vh>
<v t="ekr.20241206101345.423"><vh>&lt;&lt; Use cli: --to fmt ekr-small-test.ipynb &gt;&gt;</vh></v>
</v>
<v t="ekr.20241206101345.424"><vh>--- jupytext: files</vh>
<v t="ekr.20241206101345.425"><vh>##@jupytext c:/test/minimal_test.ipynb</vh></v>
<v t="ekr.20241206101345.426"><vh>@path C:/Test/</vh>
<v t="ekr.20241206101345.427"><vh>##@jupytext Notebooks-intro.ipynb</vh></v>
</v>
<v t="ekr.20241206101345.428"><vh>##@jupytext c:/test/ekr-small-test.ipynb</vh>
<v t="ekr.20241206101345.429"><vh>&lt;&lt; preamble &gt;&gt;</vh></v>
<v t="ekr.20241206101345.430"><vh>python</vh>
<v t="ekr.20241206101345.431"><vh>node 1</vh></v>
<v t="ekr.20241206101345.432"><vh>node 2</vh></v>
</v>
<v t="ekr.20241206101345.433"><vh>markdown</vh></v>
</v>
<v t="ekr.20241206101345.434"><vh>##@jupytext c:/test/example-data-analysis.ipynb</vh>
<v t="ekr.20241206101345.435"><vh>&lt;&lt; prefix &gt;&gt;</vh></v>
<v t="ekr.20241206101345.436"><vh># Loading and Analyzing Data</vh>
<v t="ekr.20241206101345.437"><vh>programmers like shortening names for</vh></v>
</v>
<v t="ekr.20241206101345.438"><vh>## Load the air quality data</vh>
<v t="ekr.20241206101345.439"><vh>Download the data from the EPA website</vh></v>
<v t="ekr.20241206101345.440"><vh>air_2019_df =</vh></v>
<v t="ekr.20241206101345.441"><vh>"{} rows for 2019, {} rows for</vh></v>
</v>
<v t="ekr.20241206101345.442"><vh>## Aggregate and average MA data by city</vh>
<v t="ekr.20241206101345.443"><vh>Step 1 - filter the data down by state</vh></v>
<v t="ekr.20241206101345.444"><vh>now trim down to just the columns we</vh></v>
<v t="ekr.20241206101345.445"><vh>now group all the records by city and</vh></v>
</v>
<v t="ekr.20241206101345.446"><vh>## Visually Inspect the Data</vh>
<v t="ekr.20241206101345.447"><vh>alt.Chart(ma_city_avg_df,</vh></v>
</v>
</v>
</v>
<v t="ekr.20241206101345.448"><vh>##@jupytext c:/test/example-data-analysis.ipynb</vh>
<v t="ekr.20241206101345.449"><vh>&lt;&lt; prefix &gt;&gt;</vh></v>
<v t="ekr.20241206101345.450"><vh># Loading and Analyzing Data</vh></v>
<v t="ekr.20241206101345.451"><vh>programmers like shortening names for</vh></v>
<v t="ekr.20241206101345.452"><vh>## Load the air quality data</vh></v>
<v t="ekr.20241206101345.453"><vh>Download the data from the EPA website</vh></v>
<v t="ekr.20241206101345.454"><vh>air_2019_df =</vh></v>
<v t="ekr.20241206101345.455"><vh>"{} rows for 2019, {} rows for</vh></v>
<v t="ekr.20241206101345.456"><vh>## Aggregate and average MA data by city</vh></v>
<v t="ekr.20241206101345.457"><vh>Step 1 - filter the data down by state</vh></v>
<v t="ekr.20241206101345.458"><vh>now trim down to just the columns we</vh></v>
<v t="ekr.20241206101345.459"><vh>now group all the records by city and</vh></v>
<v t="ekr.20241206101345.460"><vh>## Visually Inspect the Data</vh></v>
<v t="ekr.20241206101345.461"><vh>alt.Chart(ma_city_avg_df,</vh></v>
</v>
<v t="ekr.20241206101345.462"><vh>read .ipynb into a node</vh></v>
<v t="ekr.20241206101345.463"><vh>##@clean C:/Users/Dev/EKR-Study/test</vh></v>
<v t="ekr.20241206101345.464"><vh>##@jupytext C:/Users/Dev/EKR-Study/test.ipynb</vh>
<v t="ekr.20241206101345.465"><vh>&lt;&lt; prefix &gt;&gt;</vh></v>
<v t="ekr.20241206101345.466"><vh>imports</vh></v>
<v t="ekr.20241206101345.467"><vh>EKR: new</vh></v>
<v t="ekr.20241206101345.468"><vh>Images</vh></v>
<v t="ekr.20241206101345.469"><vh>Figures 1 &amp; 2</vh></v>
</v>
</v>
<v t="ekr.20241206101345.470"><vh>--- most recent coloring tests</vh>
<v t="ekr.20241206101345.471"></v>
<v t="ekr.20241206101345.472"></v>
<v t="ekr.20241206101345.475"></v>
<v t="ekr.20241206101345.474"><vh>GLSL</vh></v>
<v t="ekr.20241206101345.475"></v>
<v t="ekr.20241206101345.476"><vh>js</vh></v>
<v t="ekr.20241206101345.477"><vh>html w/ js</vh></v>
<v t="ekr.20241206101345.478"><vh>html</vh></v>
<v t="ekr.20241206101345.479"><vh>vue.js (using @language)</vh></v>
<v t="ekr.20241206101345.480"><vh>vue.js</vh></v>
<v t="ekr.20241206101345.481"><vh>html error</vh></v>
<v t="ekr.20241206101345.482"><vh>css</vh></v>
</v>
</v>
<v t="ekr.20241206101345.491"><vh>ai script</vh>
<v t="ekr.20241206101345.492"><vh>function: summarize_and_reformat</vh></v>
<v t="ekr.20241206101345.493"><vh>function: process_node_with_openai</vh></v>
<v t="ekr.20241206101345.494"><vh>function: add_openai_command</vh></v>
</v>
<v t="ekr.20251214045245.1"><vh>--- Leo test cases</vh>
<v t="ekr.20251214045245.2"><vh>--- beautify test</vh>
<v t="ekr.20251214045245.3"><vh>&lt;&lt; section 1 &gt;&gt;</vh></v>
<v t="ekr.20251214045245.4"><vh>&lt;&lt; test &gt;&gt;</vh></v>
<v t="ekr.20251214045245.5"><vh>Node 1</vh></v>
<v t="ekr.20251214045245.6"><vh>Node 2</vh></v>
</v>
<v t="ekr.20251214045245.7"><vh>--- Rust colorizer bug</vh>
<v t="ekr.20251214045245.8"><vh>bug example: wrong (now works)</vh></v>
<v t="ekr.20251214045245.9"><vh>bug example: works</vh></v>
</v>
<v t="ekr.20251214045245.10"><vh>template strings</vh></v>
<v t="ekr.20251214045245.11"><vh>Tests: find-var</vh>
<v t="ekr.20251214045245.12"><vh>find-var test (python)</vh></v>
<v t="ekr.20251214045245.13"><vh>find-var test (rust)</vh></v>
</v>
<v t="ekr.20251214045245.14"><vh>Tests: graphics</vh>
<v t="ekr.20251214045245.15"><vh>run turtledemo/penrose.py</vh></v>
<v t="ekr.20251214045245.16"><vh>mathjax rendering (VR3 only)</vh>
<v t="ekr.20251214045245.17"><vh>markdown example from .ipynb file</vh></v>
<v t="ekr.20251214045245.18"><vh>mathjax-test2.html (works)</vh></v>
<v t="ekr.20251214045245.19"><vh>VR3: test TeX markup (works)</vh></v>
</v>
<v t="ekr.20251214045245.20"><vh>working VR examples</vh>
<v t="ekr.20251214045245.21"><vh>at-html tests</vh>
<v t="ekr.20251214045245.22"><vh>@html sphinx Link</vh></v>
<v t="ekr.20251214045245.23"><vh>@html display Leo tree (cool)</vh></v>
<v t="ekr.20251214045245.24"><vh>@html Leo's home page</vh></v>
</v>
<v t="ekr.20251214045245.25"><vh>pdf tests</vh>
<v t="ekr.20251214045245.26"><vh>@pdf temp</vh></v>
<v t="ekr.20251214045245.27"><vh>@pdf test</vh></v>
</v>
<v t="ekr.20251214045245.28"><vh>at-image tests</vh>
<v t="ekr.20251214045245.29"><vh>@image LeoSplash (works, but very large)</vh></v>
<v t="ekr.20251214045245.30"><vh>@path C:\Repos\leo-editor\leo\Icons</vh>
<v t="ekr.20251214045245.31"><vh>@image LeoSplash (test: using @path)</vh></v>
</v>
</v>
<v t="ekr.20251214045245.32"><vh>at-pyplot tests</vh>
<v t="ekr.20251214045245.33"><vh>@pyplot barchar_demo</vh></v>
<v t="ekr.20251214045245.34"><vh>@pyplot basic_example 1</vh></v>
<v t="ekr.20251214045245.35"><vh>@pyplot basic_example 2</vh></v>
<v t="ekr.20251214045245.36"><vh>@pyplot matplotlib animate_decay</vh>
<v t="ekr.20251214045245.37"><vh>data_gen</vh></v>
<v t="ekr.20251214045245.38"><vh>init</vh></v>
<v t="ekr.20251214045245.39"><vh>run</vh></v>
</v>
<v t="ekr.20251214045245.40"><vh>@pyplot two graphs in same window</vh></v>
</v>
<v t="ekr.20251214045245.41"><vh>at-svg tests</vh>
<v t="ekr.20251214045245.42"><vh>@svg bubbles sources</vh></v>
<v t="ekr.20251214045245.43"><vh>@svg bubbles.svg</vh></v>
</v>
<v t="ekr.20251214045245.44"><vh>docstring test</vh></v>
<v t="ekr.20251214045245.45"><vh>markdown tests</vh>
<v t="ekr.20251214045245.46"><vh>markdown test (including url)</vh></v>
<v t="ekr.20251214045245.47"><vh>markdown test (math)</vh></v>
<v t="ekr.20251214045245.48"><vh>markdown test (link)</vh></v>
</v>
<v t="ekr.20251214045245.49"><vh>rst tests</vh>
<v t="ekr.20251214045245.50"><vh>rst test (including url)</vh></v>
<v t="ekr.20251214045245.51"><vh>rst example</vh>
<v t="ekr.20251214045245.52"><vh>python</vh></v>
</v>
<v t="ekr.20251214045245.53"><vh>python code block in rst</vh></v>
</v>
<v t="ekr.20251214045245.54"><vh>URLs tests</vh>
<v t="ekr.20251214045245.55"><vh>@url Leo's home page (md)</vh></v>
<v t="ekr.20251214045245.56"><vh>@url Leo's home page (rST)</vh></v>
<v t="ekr.20251214045245.57"><vh>@url mail (rst)</vh></v>
<v t="ekr.20251214045245.58"><vh>@url weather (md)</vh></v>
</v>
</v>
</v>
<v t="ekr.20251214045245.59"><vh>Tests: Jupytext</vh>
<v t="ekr.20251214045245.60"><vh>--- jupytext: files</vh>
<v t="ekr.20251214045245.61"><vh>##@jupytext c:/test/minimal_test.ipynb</vh></v>
<v t="ekr.20251214045245.62"><vh>@path C:/Test/</vh>
<v t="ekr.20251214045245.63"><vh>##@jupytext Notebooks-intro.ipynb</vh></v>
</v>
<v t="ekr.20251214045245.64"><vh>##@jupytext c:/test/ekr-small-test.ipynb</vh>
<v t="ekr.20251214045245.65"><vh>&lt;&lt; preamble &gt;&gt;</vh></v>
<v t="ekr.20251214045245.66"><vh>python</vh>
<v t="ekr.20251214045245.67"><vh>node 1</vh></v>
<v t="ekr.20251214045245.68"><vh>node 2</vh></v>
</v>
<v t="ekr.20251214045245.69"><vh>markdown</vh></v>
</v>
<v t="ekr.20251214045245.70"><vh>##@jupytext c:/test/example-data-analysis.ipynb</vh>
<v t="ekr.20251214045245.71"><vh>&lt;&lt; prefix &gt;&gt;</vh></v>
<v t="ekr.20251214045245.72"><vh># Loading and Analyzing Data</vh>
<v t="ekr.20251214045245.73"><vh>programmers like shortening names for</vh></v>
</v>
<v t="ekr.20251214045245.74"><vh>## Load the air quality data</vh>
<v t="ekr.20251214045245.75"><vh>Download the data from the EPA website</vh></v>
<v t="ekr.20251214045245.76"><vh>air_2019_df =</vh></v>
<v t="ekr.20251214045245.77"><vh>"{} rows for 2019, {} rows for</vh></v>
</v>
<v t="ekr.20251214045245.78"><vh>## Aggregate and average MA data by city</vh>
<v t="ekr.20251214045245.79"><vh>Step 1 - filter the data down by state</vh></v>
<v t="ekr.20251214045245.80"><vh>now trim down to just the columns we</vh></v>
<v t="ekr.20251214045245.81"><vh>now group all the records by city and</vh></v>
</v>
<v t="ekr.20251214045245.82"><vh>## Visually Inspect the Data</vh>
<v t="ekr.20251214045245.83"><vh>alt.Chart(ma_city_avg_df,</vh></v>
</v>
</v>
</v>
<v t="ekr.20251214045245.84"><vh>##@jupytext c:/test/example-data-analysis.ipynb</vh>
<v t="ekr.20251214045245.85"><vh>&lt;&lt; prefix &gt;&gt;</vh></v>
<v t="ekr.20251214045245.86"><vh># Loading and Analyzing Data</vh></v>
<v t="ekr.20251214045245.87"><vh>programmers like shortening names for</vh></v>
<v t="ekr.20251214045245.88"><vh>## Load the air quality data</vh></v>
<v t="ekr.20251214045245.89"><vh>Download the data from the EPA website</vh></v>
<v t="ekr.20251214045245.90"><vh>air_2019_df =</vh></v>
<v t="ekr.20251214045245.91"><vh>"{} rows for 2019, {} rows for</vh></v>
<v t="ekr.20251214045245.92"><vh>## Aggregate and average MA data by city</vh></v>
<v t="ekr.20251214045245.93"><vh>Step 1 - filter the data down by state</vh></v>
<v t="ekr.20251214045245.94"><vh>now trim down to just the columns we</vh></v>
<v t="ekr.20251214045245.95"><vh>now group all the records by city and</vh></v>
<v t="ekr.20251214045245.96"><vh>## Visually Inspect the Data</vh></v>
<v t="ekr.20251214045245.97"><vh>alt.Chart(ma_city_avg_df,</vh></v>
</v>
<v t="ekr.20251214045245.98"><vh>read .ipynb into a node</vh></v>
<v t="ekr.20251214045245.99"><vh>##@clean C:/Users/Dev/EKR-Study/test</vh></v>
<v t="ekr.20251214045245.100"><vh>##@jupytext C:/Users/Dev/EKR-Study/test.ipynb</vh>
<v t="ekr.20251214045245.101"><vh>&lt;&lt; prefix &gt;&gt;</vh></v>
<v t="ekr.20251214045245.102"><vh>imports</vh></v>
<v t="ekr.20251214045245.103"><vh>EKR: new</vh></v>
<v t="ekr.20251214045245.104"><vh>Images</vh></v>
<v t="ekr.20251214045245.105"><vh>Figures 1 &amp; 2</vh></v>
</v>
<v t="ekr.20251214045245.106"><vh>@command test.ipynb</vh></v>
<v t="ekr.20251214045245.107"><vh>### @jupytext C:/Repos/EKR-Math/python/CH01/CH01_SEC02.ipynb</vh>
<v t="ekr.20251214045245.108"><vh>&lt;&lt; prefix &gt;&gt;</vh></v>
<v t="ekr.20251214045245.109"><vh>imports</vh></v>
<v t="ekr.20251214045245.110"><vh>Graphs</vh></v>
<v t="ekr.20251214045245.111"><vh>Images</vh></v>
<v t="ekr.20251214045245.112"><vh>Figures 1 &amp; 2</vh></v>
</v>
</v>
<v t="ekr.20251214045245.113"><vh>Tests: parse-body</vh>
<v t="ekr.20251214045245.114"><vh>test of python parse-body</vh></v>
<v t="ekr.20251214045245.115"><vh>test of rust parse-body</vh></v>
<v t="ekr.20251214045245.116"><vh>test of typescript parse-body</vh></v>
</v>
<v t="ekr.20251214045245.117"><vh>Tests: Syntax coloring</vh>
<v t="ekr.20251214045245.118"><vh> dump colorer.configDict</vh></v>
<v t="ekr.20251214045245.119"><vh>--- html tests</vh>
<v t="ekr.20251214045245.120"><vh>test: html</vh></v>
<v t="ekr.20251214045245.121"><vh>test: html error</vh></v>
<v t="ekr.20251214045245.122"><vh>test: html test</vh></v>
<v t="ekr.20251214045245.123"><vh>test: html w/ js</vh></v>
</v>
<v t="ekr.20251214045245.124"><vh>--- jupytext tests</vh>
<v t="ekr.20251214045245.125"><vh>test: jupytext (@others)</vh></v>
<v t="ekr.20251214045245.126"><vh>test: jupytext: both</vh></v>
<v t="ekr.20251214045245.127"><vh>test: jupytext: md</vh></v>
<v t="ekr.20251214045245.128"><vh>test: jupytext: python</vh></v>
</v>
<v t="ekr.20251214045245.129"><vh>test: @killcolor</vh></v>
<v t="ekr.20251214045245.130"><vh>test: @nocolor &amp; @color</vh></v>
<v t="ekr.20251214045245.131"><vh>test: at-language md/python</vh></v>
<v t="ekr.20251214045245.132"><vh>test: c</vh></v>
<v t="ekr.20251214045245.133"><vh>test: csharp colorizer test</vh></v>
<v t="ekr.20251214045245.134"><vh>test: css</vh></v>
<v t="ekr.20251214045245.135"><vh>test: cweb</vh></v>
<v t="ekr.20251214045245.136"><vh>test: docstring &amp; docpart tests</vh></v>
<v t="ekr.20251214045245.137"><vh>test: elisp (lisp)</vh></v>
<v t="ekr.20251214045245.138"><vh>test: GLSL</vh></v>
<v t="ekr.20251214045245.139"><vh>test: js</vh></v>
<v t="ekr.20251214045245.140"><vh>test: md</vh></v>
<v t="ekr.20251214045245.141"><vh>test: nim</vh></v>
<v t="ekr.20251214045245.142"><vh>test: python</vh></v>
<v t="ekr.20251214045245.143"><vh>test: python &amp; md</vh></v>
<v t="ekr.20251214045245.144"><vh>test: rest in rust</vh></v>
<v t="ekr.20251214045245.145"><vh>test: rest/python</vh></v>
<v t="ekr.20251214045245.146"><vh>test: rust</vh></v>
<v t="ekr.20251214045245.147"><vh>test: vue.js</vh></v>
<v t="ekr.20251214045245.148"><vh>test: vue.js (using @language)</vh></v>
<v t="ekr.20251214045245.149"><vh>test: wikiview test</vh></v>
</v>
<v t="ekr.20251214045245.150"><vh>test of two @doc parts</vh></v>
</v>
</v>
</vnodes>
<tnodes>
<t tx="ekr.20180311131353.1" str_leo_pos="3,11"></t>
<t tx="ekr.20180311195905.1">"""
Changes LaTeX section definition levels in the subtree of an @clean file node to their subtree level.
Only one LaTeX section level can therefore be used within a single node body.
"""

import re

section_levels = {
    1: 'chapter',
    2: 'section',
    3: 'subsection',
    4: 'subsubsection',
    5: 'paragraph',
    6: 'subparagraph'
}

def latex_convert_section_levels(p, adjusted_level_name):
    """ Replaces LaTeX section definition levels found on a single line (re multiline mode).
    Returns the modified node body."""
    return re.sub(r'\\(chapter|section|subsection|subsubsection|paragraph|subparagraph)(\[.*?\])?({.*})',
        r'\\'+adjusted_level_name+r'\g&lt;2&gt;\g&lt;3&gt;', p.b, re.M)

u, undoType = c.undoer, 'change-latex'
h = p.h.strip()
if g.match_word(h, 0, '@clean') and h.endswith('.tex'):
    bunch = u.beforeChangeTree(c.p)
    changed, dirtyVnodeList = 0, []
    root_level = p.level()
    for p in p.subtree():
        level = p.level() - root_level
        if level &lt; 7:
            level_name = section_levels[level]
        else:
            level_name = 'subparagraph'
        s = latex_convert_section_levels(p, level_name)
        if s != p.b:
            bunch2 = u.beforeChangeNodeContents(p)
            p.b = s
            u.afterChangeNodeContents(p, undoType, bunch2, dirtyVnodeList=dirtyVnodeList)
            p.v.setDirty()
            changed += 1
    if changed:
        u.afterChangeTree(c.p, undoType, bunch)
        g.es('Changed %s node%s.' % (changed, g.plural(changed)))
    else:
        g.es('No nodes changed')
else:
    g.es('not a LaTeX file node?')
    
@language python
</t>
<t tx="ekr.20180311200239.1">@language tex
@others
</t>
<t tx="ekr.20180311200312.1">\paragraph{one}

</t>
<t tx="ekr.20180311200318.1">\paragraph{two}


</t>
<t tx="ekr.20180311210803.1"></t>
<t tx="ekr.20230622112641.1"># Note: Modify @data unl-path-prefixes (in this file)
# To match the paths on your machine.</t>
<t tx="ekr.20230622112649.1"># These links will be active only if the @&lt;file&gt; node exists.

# Absolute file names

# Exists, non-empty.
C:/Repos/leo-editor/leo/plugins/importers/__init__.py:1:0:flake8 error

# Exists, empty.
C:/Repos/leo-editor/leo/plugins/leo_babel/__init__.py:1:0:flake8 error

# Large.
# C:/Repos/leo-editor/leo/core/leoGlobals.py:100:0:flake8 error
</t>
<t tx="ekr.20230622114548.1"></t>
<t tx="ekr.20230622114556.1">h = 'Error messages (copy to log)'
p = g.findNodeAnywhere(c, h)
if p:
    lines = [z for z in g.splitLines(p.b) if not z.strip().startswith('#')]
    c.frame.log.put_html_links(''.join(lines))
else:
    print('Not found:', h)
</t>
<t tx="ekr.20230623064935.1">parse-body
diff-marked-nodes
refresh-from-disk
convert-unls
copy-test</t>
<t tx="ekr.20230623141357.1">https://github.com/leo-editor/leo-editor/pull/3215/files

# Exists: Recent tests.
unl:gnx://#ekr.20180311131424.1

# Error mssages (copy to log)
unl:gnx://#ekr.20230622112649.1

# Bad gnx
unl:gnx://xyzzy.leo#.20230622112649.1

# In LeoDocs.leo: will work only if LeoDocs.leo is open!
unl:gnx://#ekr.20100805171546.4412

</t>
<t tx="ekr.20230625084119.1"># Shorter UNL:
unl://#Coloring tests--&gt;Syntax coloring template

# Exists, non-empty.
unl://C:/Repos/leo-editor/leo/test/test.leo#@file ../plugins/importers/__init__.py
unl://#@file ../plugins/importers/__init__.py

# Exists, empty.
unl://C:/Repos/leo-editor/leo/test/test.leo#@clean ../plugins/leo_babel/__init__.py
unl://#@clean ../plugins/leo_babel/__init__.py

# Exists
unl://C:/Repos/leo-editor/leo/test/test.leo#Viewrendered examples
unl://#Viewrendered examples

unl://C:/Repos/leo-editor/leo/test/test.leo#Viewrendered examples--&gt;Python code
unl://#Viewrendered examples--&gt;Python code
</t>
<t tx="ekr.20230627150454.1"># Exists: Recent tests
unl:gnx://test.leo#ekr.20180311131424.1

# Error mssages (copy to log)
unl:gnx://test.leo#ekr.20230622112649.1

# The following links depend on @data unl-path-prefixes.

# These links should open LeoDocs.leo if it is not already open.

# In LeoDocs.leo: Leo 6.7.3 release notes
unl:gnx://LeoDocs.leo#ekr.20230409052507.1

# In LeoDocs.leo: ** Read me first **
unl:gnx://LeoDocs.leo#ekr.20050831195449

# Bad gnx: xyzzy.leo does not exist.
unl:gnx://xyzzy.leo#.20230622112649.1</t>
<t tx="ekr.20230628090119.1"># The following links depend on @data unl-path-prefixes.

unl://LeoDocs.leo#Release Notes--&gt;Leo 6.7.3 release notes

# In LeoDocs.leo
unl://LeoDocs.leo#Web pages

# In LeoDocs.leo. Will work only if LeoDocs.leo is open!
unl://#Web pages
</t>
<t tx="ekr.20230707065449.1"># These unls will work only on EKR's machine.
# They are a test of absolute matches in g.openUNLFile.

# Exists: Recent tests
unl:gnx://c:\Repos\leo-editor\leo\test\test.leo#ekr.20180311131424.1

# Error mssages (copy to log)
unl:gnx://c:\Repos\leo-editor\leo\test\test.leo#ekr.20230622112649.1

# Should fail
unl:gnx://c:\Repos\leo-editor\leo\doc\test.leo#ekr.20230622112649.1

# The following links should open LeoDocs.leo if it is not already open.

# In LeoDocs.leo: Leo 6.7.3 release notes
unl:gnx://c:\Repos\leo-editor\leo\doc\LeoDocs.leo#ekr.20230409052507.1

# In LeoDocs.leo: ** Read me first **
unl:gnx://c:\Repos\leo-editor\leo\doc\LeoDocs.leo#ekr.20050831195449

# Bad gnx: xyzzy.leo does not exist.
unl:gnx://xyzzy.leo#.20230622112649.1</t>
<t tx="ekr.20231231043342.1"># Exists: Recent tests
unl:gnx://#ekr.20180311131424.1

# Error mssages (copy to log)
unl:gnx://#ekr.20230622112649.1

# The following will work only if LeoDocs.leo is open!

# In LeoDocs.leo: Leo 6.7.3 release notes
unl:gnx://#ekr.20230409052507.1

# In LeoDocs.leo: ** Read me first **
unl:gnx://#ekr.20050831195449
</t>
<t tx="ekr.20231231164138.1"># These data work on EKR's machine.  Modify for your own!

# lines have the form:
# x.leo: &lt;absolute path to x.leo&gt;

test.leo:    c:/Repos/leo-editor/leo/test
LeoDocs.leo: c:/Repos/leo-editor/leo/doc
</t>
<t tx="ekr.20240226115140.1"># In LeoDocs.leo: Leo 6.7.3 release notes
unl:gnx://../doc/LeoDocs.leo#ekr.20230409052507.1

# In LeoDocs.leo: ** Leo's Documentation **
unl:gnx://../doc/LeoDocs.leo#ekr.20040414161647

# Just open LeoDocs.leo.
unl:gnx://../doc/LeoDocs.leo#</t>
<t tx="ekr.20241206101317.1" annotate="7d71002858080000007072696f7269747971014d0f27580a000000707269736574646174657102580a000000323032342d30342d31307103752e"></t>
<t tx="ekr.20241206101317.10">@language python
</t>
<t tx="ekr.20241206101317.11">Callout('Hello. This tutorial introduces Leo')
# Callout("First, I'll increase the text size for easier viewing")
# demo.next(wait=1.0)
</t>
<t tx="ekr.20241206101317.12"># Create, move, promote, demote, hoist.
demo.retain(Title('Leo is a full featured outliner.'))
demo.wait(1.0)
###
demo.insert_node('a new node', keys=True, speed=10.0)
###
c.moveOutlineRight()
###
demo.end() # Test of early exit.
###
demo.insert_node('another headline')
###
demo.insert_node('yet another node')
###
p = g.findNodeInTree(c, demo.root, 'a new node')
assert p, 'a new node'
c.selectPosition(p)
demo.wait(0.25)
###
c.demote()
demo.wait(1.0)
###
demo.delete_retained_widgets()
</t>
<t tx="ekr.20241206101317.13"># The body pane shows the text of the selected outline node.</t>
<t tx="ekr.20241206101317.14"># Section refs, @others, @file
</t>
<t tx="ekr.20241206101317.15"></t>
<t tx="ekr.20241206101317.16"># Callout('Thanks for watching')
###
# demo.next()</t>
<t tx="ekr.20241206101317.17">@language rest
@wrap


</t>
<t tx="ekr.20241206101317.18">@language python
if c.isChanged(): c.save()
&lt;&lt; imports &gt;&gt;
&lt;&lt; class IntroSlides &gt;&gt;
&lt;&lt; main &gt;&gt;
# Do NOT use @others here.
try:
    # The *same* command/key binding calls both demo-start and demo.next.
    if getattr(g.app, 'demo', None):
        g.app.demo.next()
    else:
        demo = IntroSlides(c)
        main(c, demo,
            auto_run=False,
            hoist_node = "Leo's Main Window",
            script_name='intro-slides-script',
        )
except Exception:
    # Exception can happen during startup.
    g.app.demo = None
    raise
</t>
<t tx="ekr.20241206101317.19">from leo.core.leoQt import QtGui
import leo.plugins.demo as demo_module
# import imp
# imp.reload(demo_module)</t>
<t tx="ekr.20241206101317.2"></t>
<t tx="ekr.20241206101317.20">class IntroSlides (demo_module.Demo):
    
    @others
</t>
<t tx="ekr.20241206101317.21">def setup(self, p=None):
    c = self.c
    self.end_on_exception = True # Good for debugging.
    self.delta = 0
    demo.set_text_delta(self.delta)
    # self.set_youtube_position()
    if hasattr(self, 'hoist_node'):
        c.selectPosition(self.hoist_node)
        c.hoist()
    c.redraw()
</t>
<t tx="ekr.20241206101317.22">def setup_script(self):
    self.delete_widgets()
</t>
<t tx="ekr.20241206101317.23">def teardown(self):
    c = self.c
    self.delete_all_widgets()
    if self.delta &gt; 0:
        self.set_text_delta(-self.delta)
    if self.hoist_node:
        c.selectPosition(self.hoist_node)
        c.dehoist()
    c.redraw()
</t>
<t tx="ekr.20241206101317.24">def teardown_script(self):
    if self.auto_run:
        # default wait.
        self.wait(0.5)</t>
<t tx="ekr.20241206101317.25">def main(c, demo, script_name, auto_run=False, hoist_node=None):
    g.cls()
    k = c.k
    class_name = demo.__class__.__name__
    c.frame.log.clearTab('Log')
    g.es_print('Starting', class_name)
    k.demoNextKey = k.strokeFromSetting('Ctrl-9')
        # Tell k.masterKeyHandler to process Ctrl-9 immediately.
        # Binding demo-next in a setting does *not* work.
    h = '@button %s @key=Ctrl-9' % class_name
    p = g.findNodeAnywhere(c, h)
    assert p, h
    script_tree = g.findNodeInTree(c, p, script_name)
    assert script_tree, repr(script_name)
    demo.hoist_node = hoist_node and g.findNodeInTree(c, p, hoist_node)
    demo.start(script_tree, auto_run=auto_run)</t>
<t tx="ekr.20241206101317.26"></t>
<t tx="ekr.20241206101317.27">@language python
</t>
<t tx="ekr.20241206101317.28">Callout("This is Leo's main window")

###
demo.next()</t>
<t tx="ekr.20241206101317.29"></t>
<t tx="ekr.20241206101317.3">if c.isChanged(): c.save()
&lt;&lt; imports &gt;&gt;
&lt;&lt; class MyDemo &gt;&gt;
# Don't use @others here.
# The *same* command/key binding calls both demo-start and demo.next.
try:
    if getattr(g.app, 'demo', None):
        g.app.demo.next()
    else:
        g.cls()
        c.frame.log.clearTab('Log')
        g.es_print('Starting MyDemo')
        c.k.demoNextKey = c.k.strokeFromSetting('Ctrl-9')
            # Tell k.masterKeyHandler to process Ctrl-9 immediately.
            # Binding demo-next in a setting does *not* work.
        demo = MyDemo(c)
        p = g.findNodeAnywhere(c, '@button Demo @key=Ctrl-9')
        script_tree = g.findNodeInTree(c, p, 'demo-script')
        demo.start(script_tree, auto_run=True)
except Exception:
    g.app.demo = None
    raise
</t>
<t tx="ekr.20241206101317.30">command = r'python c:\Python\Python3.13\Lib\turtledemo\penrose.py'
g.execute_shell_commands(command)

</t>
<t tx="ekr.20241206101317.31">@language html</t>
<t tx="ekr.20241206101317.32">@language md

&lt;h2&gt;Feed-forward Network Functions&lt;/h2&gt;
We will consider a basic two layer neural network model, i.e a model that maps inputs to a hidden layer and then to an output layer. We will make **the following assumptions**

1. The final output will be a vector $Y$ with $K$ elements, $y_k$, where $y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})$ is the probability that node $k$ is in class $C_1$ and $p(C_2|\mathbf{x}) = 1-p(C_1|\mathbf{x})$
2. The activation function at a given layer is an arbitrary nonlinear function of a linear combination of the inputs and parameters for that layer
3. The network is fully connected, i.e. every node at the input layer is connected to every node in the hidden layer and every node in the hidden layer is connected to every node in the output layer
4. A bias parameter is included at the hidden and output layers

Working from the input layer toward the output layer, we can build this model as follows:

&lt;h3&gt;Input Layer&lt;/h3&gt;
Assume we have an input vector $\mathbf{x} \in \Re^D$. Then the input layer consists of $D+1$ nodes where the value of the $i^{th}$ node for $i=0\ldots D$, is 0 if $i=0$ and $x_i$, i.e. the $i^{th}$ value of $\mathbf{x}$, otherwise.

&lt;h3&gt;Hidden Layer&lt;/h3&gt;
At the hidden layer we construct $M$ nodes where the value of $M$ depends on the specifics of the particular modeling problem. For each node, we define a *unit activation*, $a_m$, for $m=1\ldots M$ as &lt;br/&gt;
$a_m = \sum_{i=0}^D w_{ji}^{(1)}x_i$ &lt;br/&gt;
where the $(1)$ superscript indicates this weight is for the hidden layer. The output from each node, $z_m$, is then given by the value of a *fixed nonlinear function*, $h$, known as the *activation function*, acting on the unit activation&lt;br/&gt;
$z_m = h(a_m) = h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right)$&lt;br/&gt;
Notice that $h$ is the same function for all nodes.
&lt;h3&gt;Output Layer&lt;/h3&gt;
The process at the output layer is essentially the same as at the hidden layer. We construct $K$ nodes, where again the value of $K$ depends on the specific modeling problem. For each node, we again define a *unit activation*, $a_k$, for $k=1 \ldots K$ by&lt;br/&gt;
$a_k = \sum_{m=0}^M w_{km}^{(2)} z_m$ &lt;br/&gt;
We again apply a nonlinear activation function, say $y$, to produce the output&lt;br/&gt;
$y_k = y(a_k)$

Thus, the entire model can be summarized as a $K$ dimensional output vector $Y \in \Re^K$ where each element $y_k$ by&lt;br/&gt;
$y_k(\mathbf{x},\mathbf{w}) = y \left( \sum_{m=0}^M w_{km}^{(2)} h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right) \right)$

&lt;h3&gt;Generalizations&lt;/h3&gt;
There are a wide variety of generalizations possible for this model. Some of the more important ones for practical applications include

* Addition of hidden layers
* Inclusion of *skip-layer* connections, e.g. a connection from an input node directly to an output node
* Sparse network, i.e. not a fully connected network</t>
<t tx="ekr.20241206101317.33">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;

&lt;script type="text/x-mathjax-config"&gt;
  MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});
&lt;/script&gt;

&lt;script type="text/javascript" async
  src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_CHTML"&gt;
&lt;/script&gt;


&lt;/head&gt;

&lt;body&gt;
&lt;p&gt;&lt;mathjax&gt;

&lt;h2&gt;Feed-forward Network Functions&lt;/h2&gt;
We will consider a basic two layer neural network model, i.e a model that maps inputs to a hidden layer and then to an output layer. We will make &lt;b&gt;the following 
assumptions&lt;/b&gt;

1. The final output will be a vector $Y$ with $K$ elements, $y_k$, where $y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})$ is the probability that node $k$ is in class $C_1$ and $p(C_2|\mathbf{x}) = 1-p(C_1|\mathbf{x})$
2. The activation function at a given layer is an arbitrary nonlinear function of a linear combination of the inputs and parameters for that layer
3. The network is fully connected, i.e. every node at the input layer is connected to every node in the hidden layer and every node in the hidden layer is connected to every node in the output layer
4. A bias parameter is included at the hidden and output layers

Working from the input layer toward the output layer, we can build this model as follows:

&lt;h3&gt;Input Layer&lt;/h3&gt;
Assume we have an input vector $\mathbf{x} \in \Re^D$. Then the input layer consists of $D+1$ nodes where the value of the $i^{th}$ node for $i=0\ldots D$, is 0 if $i=0$ and $x_i$, i.e. the $i^{th}$ value of $\mathbf{x}$, otherwise.

&lt;h3&gt;Hidden Layer&lt;/h3&gt;
At the hidden layer we construct $M$ nodes where the value of $M$ depends on the specifics of the particular modeling problem. For each node, we define a *unit activation*, $a_m$, for $m=1\ldots M$ as &lt;br/&gt;
$a_m = \sum_{i=0}^D w_{ji}^{(1)}x_i$ &lt;br/&gt;
where the $(1)$ superscript indicates this weight is for the hidden layer. The output from each node, $z_m$, is then given by the value of a *fixed nonlinear function*, $h$, known as the *activation function*, acting on the unit activation&lt;br/&gt;
$z_m = h(a_m) = h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right)$&lt;br/&gt;
Notice that $h$ is the same function for all nodes.
&lt;h3&gt;Output Layer&lt;/h3&gt;
The process at the output layer is essentially the same as at the hidden layer. We construct $K$ nodes, where again the value of $K$ depends on the specific modeling problem. For each node, we again define a *unit activation*, $a_k$, for $k=1 \ldots K$ by&lt;br/&gt;
$a_k = \\sum_{m=0}^M w_{km}^{(2)} z_m$ &lt;br/&gt;
We again apply a nonlinear activation function, say $y$, to produce the output&lt;br/&gt;
$y_k = y(a_k)$

Thus, the entire model can be summarized as a $K$ dimensional output vector $Y \in \Re^K$ where each element $y_k$ by&lt;br/&gt;
$y_k(\mathbf{x},\mathbf{w}) = y \left( \sum_{m=0}^M w_{km}^{(2)} h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right) \right)$

&lt;h3&gt;Generalizations&lt;/h3&gt;
There are a wide variety of generalizations possible for this model. Some of the more important ones for practical applications include

* Addition of hidden layers
* Inclusion of *skip-layer* connections, e.g. a connection from an input node directly to an output node
* Sparse network, i.e. not a fully connected network

&lt;!-- $$ --&gt;
&lt;/mathjax&gt;&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;

</t>
<t tx="ekr.20241206101317.34">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;!-- saved from url=(0043)file:///C:/Users/tom/.leo/leo_rst_html.html --&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;&lt;head&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;

&lt;meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/"&gt;
&lt;title&gt;Formula&lt;/title&gt;
&lt;script type="text/javascript" src="./vr3-mathjax-sample_files/tex-mml-chtml.js.download"&gt;&lt;/script&gt;&lt;style type="text/css"&gt;.CtxtMenu_InfoClose {  top:.2em; right:.2em;}
.CtxtMenu_InfoContent {  overflow:auto; text-align:left; font-size:80%;  padding:.4em .6em; border:1px inset; margin:1em 0px;  max-height:20em; max-width:30em; background-color:#EEEEEE;  white-space:normal;}
.CtxtMenu_Info.CtxtMenu_MousePost {outline:none;}
.CtxtMenu_Info {  position:fixed; left:50%; width:auto; text-align:center;  border:3px outset; padding:1em 2em; background-color:#DDDDDD;  color:black;  cursor:default; font-family:message-box; font-size:120%;  font-style:normal; text-indent:0; text-transform:none;  line-height:normal; letter-spacing:normal; word-spacing:normal;  word-wrap:normal; white-space:nowrap; float:none; z-index:201;  border-radius: 15px;                     /* Opera 10.5 and IE9 */  -webkit-border-radius:15px;               /* Safari and Chrome */  -moz-border-radius:15px;                  /* Firefox */  -khtml-border-radius:15px;                /* Konqueror */  box-shadow:0px 10px 20px #808080;         /* Opera 10.5 and IE9 */  -webkit-box-shadow:0px 10px 20px #808080; /* Safari 3 &amp; Chrome */  -moz-box-shadow:0px 10px 20px #808080;    /* Forefox 3.5 */  -khtml-box-shadow:0px 10px 20px #808080;  /* Konqueror */  filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color="gray", Positive="true"); /* IE */}
&lt;/style&gt;&lt;style type="text/css"&gt;.CtxtMenu_MenuClose {  position:absolute;  cursor:pointer;  display:inline-block;  border:2px solid #AAA;  border-radius:18px;  -webkit-border-radius: 18px;             /* Safari and Chrome */  -moz-border-radius: 18px;                /* Firefox */  -khtml-border-radius: 18px;              /* Konqueror */  font-family: "Courier New", Courier;  font-size:24px;  color:#F0F0F0}
.CtxtMenu_MenuClose span {  display:block; background-color:#AAA; border:1.5px solid;  border-radius:18px;  -webkit-border-radius: 18px;             /* Safari and Chrome */  -moz-border-radius: 18px;                /* Firefox */  -khtml-border-radius: 18px;              /* Konqueror */  line-height:0;  padding:8px 0 6px     /* may need to be browser-specific */}
.CtxtMenu_MenuClose:hover {  color:white!important;  border:2px solid #CCC!important}
.CtxtMenu_MenuClose:hover span {  background-color:#CCC!important}
.CtxtMenu_MenuClose:hover:focus {  outline:none}
&lt;/style&gt;&lt;style type="text/css"&gt;.CtxtMenu_Menu {  position:absolute;  background-color:white;  color:black;  width:auto; padding:5px 0px;  border:1px solid #CCCCCC; margin:0; cursor:default;  font: menu; text-align:left; text-indent:0; text-transform:none;  line-height:normal; letter-spacing:normal; word-spacing:normal;  word-wrap:normal; white-space:nowrap; float:none; z-index:201;  border-radius: 5px;                     /* Opera 10.5 and IE9 */  -webkit-border-radius: 5px;             /* Safari and Chrome */  -moz-border-radius: 5px;                /* Firefox */  -khtml-border-radius: 5px;              /* Konqueror */  box-shadow:0px 10px 20px #808080;         /* Opera 10.5 and IE9 */  -webkit-box-shadow:0px 10px 20px #808080; /* Safari 3 &amp; Chrome */  -moz-box-shadow:0px 10px 20px #808080;    /* Forefox 3.5 */  -khtml-box-shadow:0px 10px 20px #808080;  /* Konqueror */}
.CtxtMenu_MenuItem {  padding: 1px 2em;  background:transparent;}
.CtxtMenu_MenuArrow {  position:absolute; right:.5em; padding-top:.25em; color:#666666;  font-family: null; font-size: .75em}
.CtxtMenu_MenuActive .CtxtMenu_MenuArrow {color:white}
.CtxtMenu_MenuArrow.CtxtMenu_RTL {left:.5em; right:auto}
.CtxtMenu_MenuCheck {  position:absolute; left:.7em;  font-family: null}
.CtxtMenu_MenuCheck.CtxtMenu_RTL { right:.7em; left:auto }
.CtxtMenu_MenuRadioCheck {  position:absolute; left: .7em;}
.CtxtMenu_MenuRadioCheck.CtxtMenu_RTL {  right: .7em; left:auto}
.CtxtMenu_MenuInputBox {  padding-left: 1em; right:.5em; color:#666666;  font-family: null;}
.CtxtMenu_MenuInputBox.CtxtMenu_RTL {  left: .1em;}
.CtxtMenu_MenuComboBox {  left:.1em; padding-bottom:.5em;}
.CtxtMenu_MenuSlider {  left: .1em;}
.CtxtMenu_SliderValue {  position:absolute; right:.1em; padding-top:.25em; color:#333333;  font-size: .75em}
.CtxtMenu_SliderBar {  outline: none; background: #d3d3d3}
.CtxtMenu_MenuLabel {  padding: 1px 2em 3px 1.33em;  font-style:italic}
.CtxtMenu_MenuRule {  border-top: 1px solid #DDDDDD;  margin: 4px 3px;}
.CtxtMenu_MenuDisabled {  color:GrayText}
.CtxtMenu_MenuActive {  background-color: #606872;  color: white;}
.CtxtMenu_MenuDisabled:focus {  background-color: #E8E8E8}
.CtxtMenu_MenuLabel:focus {  background-color: #E8E8E8}
.CtxtMenu_ContextMenu:focus {  outline:none}
.CtxtMenu_ContextMenu .CtxtMenu_MenuItem:focus {  outline:none}
.CtxtMenu_SelectionMenu {  position:relative; float:left;  border-bottom: none; -webkit-box-shadow:none; -webkit-border-radius:0px; }
.CtxtMenu_SelectionItem {  padding-right: 1em;}
.CtxtMenu_Selection {  right: 40%; width:50%; }
.CtxtMenu_SelectionBox {  padding: 0em; max-height:20em; max-width: none;  background-color:#FFFFFF;}
.CtxtMenu_SelectionDivider {  clear: both; border-top: 2px solid #000000;}
.CtxtMenu_Menu .CtxtMenu_MenuClose {  top:-10px; left:-10px}
&lt;/style&gt;
&lt;style type="text/css"&gt;

/* +---------------+
/* | normalize.css |
 * +---------------+
 */

/**
 * Sources
 *1. Docutils solarized-dark theme adapted from
 *    https://github.com/fladd/docutils-solarized
 *
 *2. Pygments additions by T. B. Passin.
 */

/*! normalize.css v3.0.2 | MIT License | git.io/normalize */

/**
 * 1. Set default font family to sans-serif.
 * 2. Prevent iOS text size adjust after orientation change, without disabling
 *    user zoom.
 */

html {
  font-family: sans-serif; /* 1 */
  -ms-text-size-adjust: 100%; /* 2 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/**
 * Remove default margin.
 */

body {
  margin: 0;
}

/* HTML5 display definitions
   ========================================================================== */

/**
 * Correct `block` display not defined for any HTML5 element in IE 8/9.
 * Correct `block` display not defined for `details` or `summary` in IE 10/11
 * and Firefox.
 * Correct `block` display not defined for `main` in IE 11.
 */

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
  display: block;
}

/**
 * 1. Correct `inline-block` display not defined in IE 8/9.
 * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
 */

audio,
canvas,
progress,
video {
  display: inline-block; /* 1 */
  vertical-align: baseline; /* 2 */
}

/**
 * Prevent modern browsers from displaying `audio` without controls.
 * Remove excess height in iOS 5 devices.
 */

audio:not([controls]) {
  display: none;
  height: 0;
}

/**
 * Address `[hidden]` styling not present in IE 8/9/10.
 * Hide the `template` element in IE 8/9/11, Safari, and Firefox &lt; 22.
 */

[hidden],
template {
  display: none;
}

/* Links
   ========================================================================== */

/**
 * Remove the gray background color from active links in IE 10.
 */

a {
  background-color: transparent;
}

/**
 * Improve readability when focused and also mouse hovered in all browsers.
 */

a:active,
a:hover {
  outline: 0;
}

/* Text-level semantics
   ========================================================================== */

/**
 * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
 */

abbr[title] {
  border-bottom: 1px dotted;
}

/**
 * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
 */

b,
strong {
  font-weight: bold;
}

/**
 * Address styling not present in Safari and Chrome.
 */

dfn {
  font-style: italic;
}

/**
 * Address variable `h1` font-size and margin within `section` and `article`
 * contexts in Firefox 4+, Safari, and Chrome.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/**
 * Address styling not present in IE 8/9.
 */

mark {
  background: #ff0;
  color: #000;
}

/**
 * Address inconsistent and variable font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` affecting `line-height` in all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sup {
  top: -0.5em;
}

sub {
  bottom: -0.25em;
}

/* Embedded content
   ========================================================================== */

/**
 * Remove border when inside `a` element in IE 8/9/10.
 */

img {
  border: 0;
}

/**
 * Correct overflow not hidden in IE 9/10/11.
 */

svg:not(:root) {
  overflow: hidden;
}

/* Grouping content
   ========================================================================== */

/**
 * Address margin not present in IE 8/9 and Safari.
 */

figure {
  margin: 1em 40px;
}

/**
 * Address differences between Firefox and other browsers.
 */

hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  height: 0;
}

/**
 * Contain overflow in all browsers.
 */

pre {
  overflow: auto;
}

/**
 * Address odd `em`-unit font size rendering in all browsers.
 */

code,
kbd,
pre,
samp {
  font-family: monospace, monospace;
  font-size: 1em;
}

/* Forms
   ========================================================================== */

/**
 * Known limitation: by default, Chrome and Safari on OS X allow very limited
 * styling of `select`, unless a `border` property is set.
 */

/**
 * 1. Correct color not being inherited.
 *    Known issue: affects color of disabled elements.
 * 2. Correct font properties not being inherited.
 * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
 */

button,
input,
optgroup,
select,
textarea {
  color: inherit; /* 1 */
  font: inherit; /* 2 */
  margin: 0; /* 3 */
}

/**
 * Address `overflow` set to `hidden` in IE 8/9/10/11.
 */

button {
  overflow: visible;
}

/**
 * Address inconsistent `text-transform` inheritance for `button` and `select`.
 * All other form control elements do not inherit `text-transform` values.
 * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
 * Correct `select` style inheritance in Firefox.
 */

button,
select {
  text-transform: none;
}

/**
 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
 *    and `video` controls.
 * 2. Correct inability to style clickable `input` types in iOS.
 * 3. Improve usability and consistency of cursor style between image-type
 *    `input` and others.
 */

button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
  -webkit-appearance: button; /* 2 */
  cursor: pointer; /* 3 */
}

/**
 * Re-set default cursor for disabled elements.
 */

button[disabled],
html input[disabled] {
  cursor: default;
}

/**
 * Remove inner padding and border in Firefox 4+.
 */

button::-moz-focus-inner,
input::-moz-focus-inner {
  border: 0;
  padding: 0;
}

/**
 * Address Firefox 4+ setting `line-height` on `input` using `!important` in
 * the UA stylesheet.
 */

input {
  line-height: normal;
}

/**
 * It's recommended that you don't attempt to style these elements.
 * Firefox's implementation doesn't respect box-sizing, padding, or width.
 *
 * 1. Address box sizing set to `content-box` in IE 8/9/10.
 * 2. Remove excess padding in IE 8/9/10.
 */

input[type="checkbox"],
input[type="radio"] {
  box-sizing: border-box; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Fix the cursor style for Chrome's increment/decrement buttons. For certain
 * `font-size` values of the `input`, it causes the cursor style of the
 * decrement button to change from `default` to `text`.
 */

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
 * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
 *    (include `-moz` to future-proof).
 */

input[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  -moz-box-sizing: content-box;
  -webkit-box-sizing: content-box; /* 2 */
  box-sizing: content-box;
}

/**
 * Remove inner padding and search cancel button in Safari and Chrome on OS X.
 * Safari (but not Chrome) clips the cancel button when the search input has
 * padding (and `textfield` appearance).
 */

input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * Define consistent border, margin, and padding.
 */

fieldset {
  border: 1px solid #c0c0c0;
  margin: 0 2px;
  padding: 0.35em 0.625em 0.75em;
}

/**
 * 1. Correct `color` not being inherited in IE 8/9/10/11.
 * 2. Remove padding so people aren't caught out if they zero out fieldsets.
 */

legend {
  border: 0; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Remove default vertical scrollbar in IE 8/9/10/11.
 */

textarea {
  overflow: auto;
}

/**
 * Don't inherit the `font-weight` (applied by a rule above).
 * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
 */

optgroup {
  font-weight: bold;
}

/* Tables
   ========================================================================== */

/**
 * Remove most spacing between table cells.
 */

table {
  border-collapse: collapse;
  border-spacing: 0;
}

td,
th {
  padding: 0;
}



/* +--------------------+
 * | docutils-solarized |
 * +--------------------+
 */

html {
  transform: scale(.85);
  transform-origin: 0 0;

}

/* Document Structure
 * ==================
 */

/* Document */

body {
  color: #839496;
  background: #002b36;
  /*font-family: Palatino, "Palatino LT STD", "Palatino Linotype", "Book Antiqua", Georgia, serif;*/
  font-family: Verdana, Arial, "Bitstream Vera Sans", sans-serif;
  padding: 0 5%;
  margin: 8px 0;
}

div.document {
  font-size: 1em;
  line-height:1.5;
  counter-reset: table;
  max-width: 50em;
  margin: auto;
}

.align-left  { text-align: left; }
.align-right { text-align: right; }
.align-center {
  clear: both;
  text-align: center;
}

/* Sections */

h1.title, p.subtitle {
  text-align: center;
}

h1, h2, h3, h4, h5, h6 {
  margin-top: 1em;
  margin-bottom: 0em;
}

h1.title {
  font-size: 2em;
  margin-top: 4rem;  
}
h1 + p.subtitle {
  font-size: 1.5em;
  margin-top: 1em;
  margin-bottom: 4rem;
}
h1 + p.section-subtitle { font-size: 1.6em; }
h2 + p.section-subtitle { font-size: 1.28em; }

a.toc-backref {
  color: #839496;
  text-decoration: none;
}

a.toc-backref:visited {
  color: #839496;
  text-decoration: none;
}

/* Stop floating sidebars, images and figures at section level 1,2,3 */
h1, h2, h3 { clear: both; }

/* Transitions */

hr.docutils {
  width: 100%;
  margin-top: 2em;
  margin-bottom: 2em;
  clear: both;
  border: 1px solid;
}

/* Paragraphs
 * ==========
 */

/* vertical space (parskip) */
p, ol, ul, dl,
div.line-block,
table, pre, .figure {
  margin-top: 1em;
  margin-bottom: 1em;
}

dl &gt; dd {
  margin-bottom: 1em;
}

/* titles */
p.admonition-title,
p.topic-title,
p.sidebar-title,
p.system-message-title {
  color: #ededed;
  font-weight: bold;
}

p.subtitle,
p.section-subtitle,
p.sidebar-subtitle {
  font-weight: bold;
  margin-top: -0.5em;
}

p.sidebar-subtitle {
  color: #ededed;
}

/* Warnings, Errors */
div.admonition {
  background-color: #073642;
  /*color: #ededed;*/
}

div.danger p.admonition-title,
div.error p.admonition-title,
div.system-messages h1,
div.error,
span.problematic,
p.system-message-title {
  color: #dc322f;
}

div.caution p.admonition-title,
div.attention p.admonition-title,
div.warning p.admonition-title {
  color: #cb4b16;
}

div.hint p.admonition-title,
div.tip p.admonition-title {
  color: #859900;
}

div.important p.admonition-title {
  color: #d33682;
}

div.note p.admonition-title {
  color: #2aa198;
}

/* Lists
/* =====
 */

/* compact and simple lists: no margin between items */
dl.simple &gt; dd, dl.compact &gt; dd,
.compact li, .compact ul, .compact ol
.simple  li, .simple  ul, .simple  ol,
.simple &gt; li p, .compact &gt; li p {
  margin-top: 0;
  margin-bottom: 0;
}

/* Enumerated Lists */

ol.arabic     { list-style: decimal }
ol.loweralpha { list-style: lower-alpha }
ol.upperalpha { list-style: upper-alpha }
ol.lowerroman { list-style: lower-roman }
ol.upperroman { list-style: upper-roman }

/* Definition Lists */

dl &gt; dd p:first-child { margin-top: 0; }

/* lists nested in definition lists */
dd &gt; ul, dd &gt; ol { padding-left: 0pt; }

dt span.classifier { font-style: italic }
dt span.classifier:before {
  font-style: normal;
  margin: 0.5em;
  content: ":";
}

/* Field Lists */

/* bold field name, content starts on the same line */
dl.field-list &gt; dt,
dl.option-list &gt; dt,
dl.docinfo &gt; dt,
dl.footnote &gt; dt,
dl.citation &gt; dt,
dl.description &gt; dt {
  font-weight: bold;
  clear: left;
  float: left;
  margin: 0;
  padding: 0;
  padding-right: 0.5em;
}

/* Offset for field content (corresponds to the --field-name-limit option) */
dl.field-list &gt; dd,
dl.option-list &gt; dd,
dl.docinfo &gt; dd {
  margin-left:  9em; /* ca. 14 chars in the test examples */
}

/* start field-body on a new line after long field names */
dl.field-list &gt; dd &gt; *:first-child,
dl.option-list &gt; dd &gt; *:first-child,
dl.docinfo &gt; dd  &gt; *:first-child {
  display: inline-block;
  width: 100%;
  margin: 0;
}

/* field names followed by a colon */
dl.field-list &gt; dt:after,
dl.docinfo    &gt; dt:after {
  content: ":";
}

/* example for custom field-name width */
dl.field-list.narrow &gt; dd {
  margin-left: 5em;
}

/* run-in: start field-body on same line after long field names */
dl.field-list.run-in &gt; dd p {
  display: block;
}

/* Bibliographic Fields */

/* use special field-list dl.docinfo */

pre.address {
  margin-bottom: 0;
  margin-top: 0;
  font: inherit;
}

dd.authors &gt; p { margin: 0; }

div.abstract {
  margin-top: 2em;
  margin-bottom: 2em;
  text-align: center;
}

div.dedication {
  margin-top: 2em;
  margin-bottom: 2em;
  text-align: center;
  font-style: italic;
}

div.dedication div.abstract  p.topic-title {
  font-style: normal;
}

/* Option Lists */

dl.option-list {
  margin-left: 1em;
}

dl.option-list &gt; dt {
  font-weight: normal;
}

span.option {
  color: #ededed;
  background-color: #073642;
  border: 1px solid #586e75;
  white-space: nowrap;
}

/* Text Blocks
 * ===========
 */

/* Line Blocks */

div.line-block {
  display: block;
}

div.line-block div.line-block {
  margin-top: 0;
  margin-bottom: 0;
  margin-left: 1.5em;
}

/* Literal Blocks */

pre.doctest-block,
pre.math, pre.code {
  /*color: #93a1a1;*/
  color: #ededed;
  background: #073642;
  font-family: Cousine, Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  border: 1px solid #586e75 !important;
  padding: 1em;
  border-radius: .7em;

}

pre.literal-block {
  color: #eee8d5;
  background: #586e75;
  font-family: Cousine, Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  border: 1px solid #b0b0b0;
  padding: 1em;
  border-radius: .7em;
}

/* Block Quotes */

blockquote,
div.topic {
  margin-left: 2em;
  margin-right: 2em
}

blockquote &gt; table,
div.topic &gt; table {
  margin-top: 0;
  margin-bottom: 0;
}

blockquote p.attribution,
div.topic p.attribution {
  text-align: right;
  margin-left: 20%;
}

/* Tables
 * ======
 */

/* margins and borders for "normal" tables */
table {
  border-collapse: collapse;
  border-top: 2px solid;
  border-bottom: 2px solid;
}

td, th {
  padding: 0.5ex 1ex;
  text-align: left;
/* some borders missing at some magnifications
/* in Firefox 31.5.0 and opera 10.63 */
  border-width: 1px;
}

td &gt; p:first-child, th &gt; p:first-child {
  margin-top: 0;
}

td &gt; p, th &gt; p {
  margin-bottom: 0;
}

th {
  background: #586e75;
  color: #eee8d5;
  vertical-align: top;
  border-bottom: thin solid #839496;
  text-align: center;
}

th.docinfo-name {
  text-align: right;
}


table &gt; caption {
  text-align: left;
}

table.borderless td, table.borderless th {
  border: 0;
  padding: 0;
  padding-right: 0.5em /* separate table cells */
}

/* "booktabs" style (no vertical lines) */
table.booktabs {
  border: 0;
  border-top: 2px solid;
  border-bottom: 2px solid;
  border-collapse: collapse;
}

table.booktabs * {
  border: 0;
}

table.booktabs th {
  border-bottom: thin solid;
  text-align: left;
}

/* numbered tables (counter defined in div.document) */
table.numbered &gt; caption:before {
  counter-increment: table;
  content: "Table " counter(table) ": ";
  font-weight: bold;
}

/* Explicit Markup Blocks
 * ======================
 */

/* Footnotes and Citations
 * -----------------------
 */

/* line on the left */
dl.footnote {
  padding-left: 1ex;
  border-left: solid;
  border-left-width: 2px;
  border-color: #839496;
}

dl &gt; dt.label {
  font-weight: normal;
}

dt.label &gt; span.fn-backref {
  margin: 0.2em;
}

dt.label &gt; span.fn-backref &gt; a {
  font-style: italic;
}

/* Directives
*  ----------
*/

/* Admonitions */
/* System Messages */

div.admonition,
div.system-message {
  margin: 2em;
  border: 2px solid;
  padding-right: 1em;
  padding-left: 1em;
  background-color: #073642;
  /*color: #ededed;*/
  border-color: #839496;
}

/* Body Elements
*  -------------
*/

/* Image and Figure */

img {
  vertical-align: bottom;
}

div.figure {
  margin-left: 2em;
  margin-right: 2em;
}

img.align-left,
.figure.align-left,
object.align-left {
  clear: left;
  float: left;
  margin-right: 1em
}

img.align-right,
.figure.align-right,
object.align-right {
  clear: right;
  float: right;
  margin-left: 1em
}

img.align-center,
.figure.align-center,
object.align-center {
  display: block;
  margin-left: auto;
  margin-right: auto;
}

/* reset inner alignment in figures */
div.align-right {
  text-align: inherit }

p.caption {
  font-style: italic;
}

object[type="image/svg+xml"], 
object[type="application/x-shockwave-flash"] {
  overflow: hidden;
}

/* Topic */

#table-of-contents {
  margin-top: 2em;
  margin-bottom: 2em;
}

/* Sidebar */

/* in a layout with fixed margins,		       */
/* the sidebar can be moved into the margin completely */
div.sidebar {
  border: 2px solid;
  border-color: #839496;
  padding-right: 1em;
  padding-left: 1em;
  width: 36%;
  max-width: 26em;
  float: right;
  clear: right;
  margin-left: 1em;
  margin-top: 2em;
  margin-bottom: 2em;
  background-color: #073642;
  color: #ededed;
}

p.sidebar-title { font-size: larger; }

/* Code */

pre.code, code {
  color: #ededed;
  background-color: #073642
}

pre.code .ln {color: #586e75}
pre.code .err { color: #dc322f }                    /* Error */

pre.code .keyword { color: #859900 }                      /* Keyword */
pre.code .keyword.constant { color: #859900 }                     /* Keyword.Constant */
pre.code .keyword.declaration { color: #859900 }                     /* Keyword.Declaration */
pre.code .keyword.namespace { color: #cb4b16 }                     /* Keyword.Namespace */
pre.code .keyword.pseudo { color: #cb4b16 }                     /* Keyword.Pseudo */
pre.code .keyword.reversed { color: #859900 }                     /* Keyword.Reserved */
pre.code .keyword.type { color: #859900 }                     /* Keyword.Type */

pre.code .name { color: #ededed }                      /* Name */
pre.code .name.attribue { }                                    /* Name.Attribute */
pre.code .name.bultin { color: #268bd2 }                     /* Name.Builtin */
pre.code .builtin.class { color: #268bd2 }                     /* Name.Builtin.Pseudo */
pre.code .name.class { color: #268bd2 }                     /* Name.Class */
pre.code .name.constant { color: #b58900 }                     /* Name.Constant */
pre.code .name.decorator { color: #cb4b16 }                     /* Name.Decorator */
pre.code .name.entity { color: #cb4b16 }                     /* Name.Entity */
pre.code .name.exception { color: #b58900 }                     /* Name.Exception */
pre.code .name.function { color: #268bd2 }                     /* Name.Function */
pre.code .name.label { }                                    /* Name.Label */
pre.code .name.namespace { }                                    /* Name.Namespace */
pre.code .name.other { }                                    /* Name.Other */
pre.code .name.property { color: #268bd2 }                     /* Name.Property */
pre.code .name.tag { color: #859900 }                     /* Name.Tag */
pre.code .name.variable { color: #cb4b16 }                     /* Name.Variable */
pre.code .name.variable.class { color: #268bd2 }                     /* Name.Variable.Class */
pre.code .name.variable.global { color: #268bd2 }                     /* Name.Variable.Global */
pre.code .name.variable.instance { color: #268bd2 }                     /* Name.Variable.Instance */

pre.code .literal { }                                     /* Literal */
pre.code .literal.date { }                                    /* Literal.Date */
pre.code .literal.string { color: #2aa198 }                      /* Literal.String */
pre.code .literal.string.backtick { color: #2aa198 }                     /* Literal.String.Backtick */
pre.code .literal.string.char { color: #2aa198 }                     /* Literal.String.Char */
pre.code .literal.string.doc { color: #2aa198 }                     /* Literal.String.Doc */
pre.code .literal.string.double { color: #2aa198 }                     /* Literal.String.Double */
pre.code .literal.string.escape { color: #cb4b16 }                     /* Literal.String.Escape */
pre.code .literal.string.heredoc { color: #2aa198 }                     /* Literal.String.Heredoc */
pre.code .literal.string.interpol { color: #cb4b16 }                     /* Literal.String.Interpol */
pre.code .literal.string.other { color: #2aa198 }                     /* Literal.String.Other */
pre.code .literal.string.regex { color: #2aa198 }                     /* Literal.String.Regex */
pre.code .literal.string.single { color: #2aa198 }                     /* Literal.String.Single */
pre.code .literal.string.symbol { color: #2aa198 }                     /* Literal.String.Symbol */
pre.code .literal.number { color: #2aa198 }                      /* Literal.Number */
pre.code .literal.number.bin { color: #2aa198 }                     /* Literal.Number.Bin */
pre.code .literal.number.float { color: #2aa198 }                     /* Literal.Number.Float */
pre.code .literal.number.hex { color: #2aa198 }                     /* Literal.Number.Hex */
pre.code .literal.number.integer { color: #2aa198 }                     /* Literal.Number.Integer */
pre.code .literal.number.integer.long { color: #2aa198 }                     /* Literal.Number.Integer.Long */
pre.code .literal.number.oct { color: #2aa198 }                     /* Literal.Number.Oct */

pre.code .operator { }                                     /* Operator */
pre.code .operator.word { color: #859900 }                     /* Operator.Word */

pre.code .punctiation { }                                     /* Punctuation */

pre.code .comment { color: #586e75; font-style: italic }  /* Comment */
pre.code .comment.multiline { color: #586e75; font-style: italic } /* Comment.Multiline */
pre.code .comment.preproc { color: #586e75; font-style: italic } /* Comment.Preproc */
pre.code .comment.single { color: #586e75; font-style: italic } /* Comment.Single */
pre.code .comment.special { color: #586e75; font-style: italic } /* Comment.Special */

pre.code .generic { }                                     /* Generic */
pre.code .other { }                                     /* Other */
pre.code .generic.deleted { }                                    /* Generic.Deleted */
pre.code .generic.emph { }                                    /* Generic.Emph */
pre.code .generic.error { }                                    /* Generic.Error */
pre.code .generic.heading { }                                    /* Generic.Heading */
pre.code .generic.inserted { }                                    /* Generic.Inserted */
pre.code .generic.output { }                                    /* Generic.Output */
pre.code .generic.prompt { }                                    /* Generic.Prompt */
pre.code .generic.strong { }                                    /* Generic.Strong */
pre.code .generic.subheading { }                                    /* Generic.Subheading */
pre.code .generic.traceback { }                                    /* Generic.Traceback */

/* Math */
/* styled separately (see math.css for math-output=HTML) */

/* Rubric */

p.rubric {
  font-weight: bold;
  font-size: larger;
  color: #b58900;
}

/* Epigraph */
/* Highlights */
/* Pull-Quote */
/* Compound Paragraph */
/* Container */

/* can be styled in a custom stylesheet */

/* Document Header &amp; Footer */

div.footer, div.header {
  clear: both;
}

hr.header {
  border: 1px solid;
  margin: 0;
  padding: 0;
}

hr.footer {
  border: 1px solid;
  margin: 0;
  padding: 0;
}

div.header {
  margin-top: 1em;
  margin-bottom: 4em;
}

div.footer {
  margin-top: 4em;
  margin-bottom: 1em;
}

/* Contents */

div.topic.contents {
  margin: 0; /* don't indent like a topic */
}

ul.auto-toc {
  list-style-type: none;
}

/* Inline Markup
*  =============
*/

* Emphasis */
/*   em  */
/* Strong Emphasis */
/*   strong  */
/* Interpreted Text */
/*   span.interpreted  	*/
/* Title Reference */
/*   cite   */
/* Inline Literals */

tt.literal, span.docutils.literal {
  font-family: Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  font-size: large;
  /*color: #ededed;
  background-color: #073642;
  border: 1px solid #586e75;*/
  /* possible values: normal, nowrap, pre, pre-wrap, pre-line */
  white-space: pre-wrap;
}

tt.docutils.literal {
  font-family: Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  font-size: large;
  /*color: #ededed;
  background-color: #073642;*/
  border: none; /*1px solid #586e75;*/
  /* possible values: normal, nowrap, pre, pre-wrap, pre-line */
  white-space: pre-wrap;
}


/* do not wraph a hyphens and similar: */
.literal &gt; span.pre { white-space: nowrap; }

/* Hyperlink References */

a {
  text-decoration: none;
  color: #268bd2;
}

a:hover {
  text-decoration: underline;
}

a:visited {
  color: #6c71c4;
}

/* External Targets */
/*   span.target.external   */
/* Internal Targets */
/*   span.target.internal   */
/* Footnote References */
/*   a.footnote-reference   */
/* Citation References  */
/*   a.citation-reference   */


// ============= Begin pygment-specific additions =====================
pre { line-height: 125%; }
td.linenos .normal { color: #586e75; background-color: #073642; padding-left: 5px; padding-right: 5px; }
span.linenos { color: #586e75; background-color: #073642; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.hll { background-color: #073642 }
.c { color: #586e75; font-style: italic } /* Comment */
.err { color: #839496; background-color: #dc322f } /* Error */
.esc { color: #839496 } /* Escape */
.g { color: #839496 } /* Generic */
.k { color: #859900 } /* Keyword */
.l { color: #839496 } /* Literal */
.n { color: #839496 } /* Name */
.o { color: #586e75 } /* Operator */
.x { color: #839496 } /* Other */
.p { color: #839496 } /* Punctuation */
.ch { color: #586e75; font-style: italic } /* Comment.Hashbang */
.cm { color: #586e75; font-style: italic } /* Comment.Multiline */
.cp { color: #d33682 } /* Comment.Preproc */
.cpf { color: #586e75 } /* Comment.PreprocFile */
.c1 { color: #586e75; font-style: italic } /* Comment.Single */
.cs { color: #586e75; font-style: italic } /* Comment.Special */
.gd { color: #dc322f } /* Generic.Deleted */
.ge { color: #839496; font-style: italic } /* Generic.Emph */
.gr { color: #dc322f } /* Generic.Error */
.gh { color: #839496; font-weight: bold } /* Generic.Heading */
.gi { color: #859900 } /* Generic.Inserted */
.go { color: #839496 } /* Generic.Output */
.gp { color: #268bd2; font-weight: bold } /* Generic.Prompt */
.gs { color: #839496; font-weight: bold } /* Generic.Strong */
.gu { color: #839496; text-decoration: underline } /* Generic.Subheading */
.gt { color: #268bd2 } /* Generic.Traceback */
.kc { color: #2aa198 } /* Keyword.Constant */
.kd { color: #2aa198 } /* Keyword.Declaration */
.kn { color: #cb4b16 } /* Keyword.Namespace */
.kp { color: #859900 } /* Keyword.Pseudo */
.kr { color: #859900 } /* Keyword.Reserved */
.kt { color: #b58900 } /* Keyword.Type */
.ld { color: #839496 } /* Literal.Date */
.m { color: #2aa198 } /* Literal.Number */
.s { color: #2aa198 } /* Literal.String */
.na { color: #839496 } /* Name.Attribute */
.nb { color: #268bd2 } /* Name.Builtin */
.nc { color: #268bd2 } /* Name.Class */
.no { color: #268bd2 } /* Name.Constant */
.nd { color: #268bd2 } /* Name.Decorator */
.ni { color: #268bd2 } /* Name.Entity */
.ne { color: #268bd2 } /* Name.Exception */
.nf { color: #268bd2 } /* Name.Function */
.nl { color: #268bd2 } /* Name.Label */
.nn { color: #268bd2 } /* Name.Namespace */
.nx { color: #839496 } /* Name.Other */
.py { color: #839496 } /* Name.Property */
.nt { color: #268bd2 } /* Name.Tag */
.nv { color: #268bd2 } /* Name.Variable */
.ow { color: #859900 } /* Operator.Word */
.w { color: #839496 } /* Text.Whitespace */
.mb { color: #2aa198 } /* Literal.Number.Bin */
.mf { color: #2aa198 } /* Literal.Number.Float */
.mh { color: #2aa198 } /* Literal.Number.Hex */
.mi { color: #2aa198 } /* Literal.Number.Integer */
.mo { color: #2aa198 } /* Literal.Number.Oct */
.sa { color: #2aa198 } /* Literal.String.Affix */
.sb { color: #2aa198 } /* Literal.String.Backtick */
.sc { color: #2aa198 } /* Literal.String.Char */
.dl { color: #2aa198 } /* Literal.String.Delimiter */
.sd { color: #586e75 } /* Literal.String.Doc */
.s2 { color: #2aa198 } /* Literal.String.Double */
.se { color: #2aa198 } /* Literal.String.Escape */
.sh { color: #2aa198 } /* Literal.String.Heredoc */
.si { color: #2aa198 } /* Literal.String.Interpol */
.sx { color: #2aa198 } /* Literal.String.Other */
.sr { color: #cb4b16 } /* Literal.String.Regex */
.s1 { color: #2aa198 } /* Literal.String.Single */
.ss { color: #2aa198 } /* Literal.String.Symbol */
.bp { color: #268bd2 } /* Name.Builtin.Pseudo */
.fm { color: #268bd2 } /* Name.Function.Magic */
.vc { color: #268bd2 } /* Name.Variable.Class */
.vg { color: #268bd2 } /* Name.Variable.Global */
.vi { color: #268bd2 } /* Name.Variable.Instance */
.vm { color: #268bd2 } /* Name.Variable.Magic */
.il { color: #2aa198 } /* Literal.Number.Integer.Long */

@media print {
  footer {page-break-after: always;}
}

&lt;/style&gt;
&lt;style id="MJX-CHTML-styles"&gt;
mjx-container[jax="CHTML"] {
  line-height: 0;
}

mjx-container [space="1"] {
  margin-left: .111em;
}

mjx-container [space="2"] {
  margin-left: .167em;
}

mjx-container [space="3"] {
  margin-left: .222em;
}

mjx-container [space="4"] {
  margin-left: .278em;
}

mjx-container [space="5"] {
  margin-left: .333em;
}

mjx-container [rspace="1"] {
  margin-right: .111em;
}

mjx-container [rspace="2"] {
  margin-right: .167em;
}

mjx-container [rspace="3"] {
  margin-right: .222em;
}

mjx-container [rspace="4"] {
  margin-right: .278em;
}

mjx-container [rspace="5"] {
  margin-right: .333em;
}

mjx-container [size="s"] {
  font-size: 70.7%;
}

mjx-container [size="ss"] {
  font-size: 50%;
}

mjx-container [size="Tn"] {
  font-size: 60%;
}

mjx-container [size="sm"] {
  font-size: 85%;
}

mjx-container [size="lg"] {
  font-size: 120%;
}

mjx-container [size="Lg"] {
  font-size: 144%;
}

mjx-container [size="LG"] {
  font-size: 173%;
}

mjx-container [size="hg"] {
  font-size: 207%;
}

mjx-container [size="HG"] {
  font-size: 249%;
}

mjx-container [width="full"] {
  width: 100%;
}

mjx-box {
  display: inline-block;
}

mjx-block {
  display: block;
}

mjx-itable {
  display: inline-table;
}

mjx-row {
  display: table-row;
}

mjx-row &gt; * {
  display: table-cell;
}

mjx-mtext {
  display: inline-block;
}

mjx-mstyle {
  display: inline-block;
}

mjx-merror {
  display: inline-block;
  color: red;
  background-color: yellow;
}

mjx-mphantom {
  visibility: hidden;
}

_::-webkit-full-page-media, _:future, :root mjx-container {
  will-change: opacity;
}

mjx-assistive-mml {
  position: absolute !important;
  top: 0px;
  left: 0px;
  clip: rect(1px, 1px, 1px, 1px);
  padding: 1px 0px 0px 0px !important;
  border: 0px !important;
  display: block !important;
  width: auto !important;
  overflow: hidden !important;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

mjx-assistive-mml[display="block"] {
  width: 100% !important;
}

mjx-math {
  display: inline-block;
  text-align: left;
  line-height: 0;
  text-indent: 0;
  font-style: normal;
  font-weight: normal;
  font-size: 100%;
  font-size-adjust: none;
  letter-spacing: normal;
  word-wrap: normal;
  word-spacing: normal;
  white-space: nowrap;
  direction: ltr;
  padding: 1px 0;
}

mjx-container[jax="CHTML"][display="true"] {
  display: block;
  text-align: center;
  margin: 1em 0;
}

mjx-container[jax="CHTML"][display="true"][width="full"] {
  display: flex;
}

mjx-container[jax="CHTML"][display="true"] mjx-math {
  padding: 0;
}

mjx-container[jax="CHTML"][justify="left"] {
  text-align: left;
}

mjx-container[jax="CHTML"][justify="right"] {
  text-align: right;
}

mjx-mi {
  display: inline-block;
  text-align: left;
}

mjx-c {
  display: inline-block;
}

mjx-utext {
  display: inline-block;
  padding: .75em 0 .2em 0;
}

mjx-mo {
  display: inline-block;
  text-align: left;
}

mjx-stretchy-h {
  display: inline-table;
  width: 100%;
}

mjx-stretchy-h &gt; * {
  display: table-cell;
  width: 0;
}

mjx-stretchy-h &gt; * &gt; mjx-c {
  display: inline-block;
  transform: scalex(1.0000001);
}

mjx-stretchy-h &gt; * &gt; mjx-c::before {
  display: inline-block;
  width: initial;
}

mjx-stretchy-h &gt; mjx-ext {
  /* IE */ overflow: hidden;
  /* others */ overflow: clip visible;
  width: 100%;
}

mjx-stretchy-h &gt; mjx-ext &gt; mjx-c::before {
  transform: scalex(500);
}

mjx-stretchy-h &gt; mjx-ext &gt; mjx-c {
  width: 0;
}

mjx-stretchy-h &gt; mjx-beg &gt; mjx-c {
  margin-right: -.1em;
}

mjx-stretchy-h &gt; mjx-end &gt; mjx-c {
  margin-left: -.1em;
}

mjx-stretchy-v {
  display: inline-block;
}

mjx-stretchy-v &gt; * {
  display: block;
}

mjx-stretchy-v &gt; mjx-beg {
  height: 0;
}

mjx-stretchy-v &gt; mjx-end &gt; mjx-c {
  display: block;
}

mjx-stretchy-v &gt; * &gt; mjx-c {
  transform: scaley(1.0000001);
  transform-origin: left center;
  overflow: hidden;
}

mjx-stretchy-v &gt; mjx-ext {
  display: block;
  height: 100%;
  box-sizing: border-box;
  border: 0px solid transparent;
  /* IE */ overflow: hidden;
  /* others */ overflow: visible clip;
}

mjx-stretchy-v &gt; mjx-ext &gt; mjx-c::before {
  width: initial;
  box-sizing: border-box;
}

mjx-stretchy-v &gt; mjx-ext &gt; mjx-c {
  transform: scaleY(500) translateY(.075em);
  overflow: visible;
}

mjx-mark {
  display: inline-block;
  height: 0px;
}

mjx-mn {
  display: inline-block;
  text-align: left;
}

mjx-msup {
  display: inline-block;
  text-align: left;
}

mjx-mfrac {
  display: inline-block;
  text-align: left;
}

mjx-frac {
  display: inline-block;
  vertical-align: 0.17em;
  padding: 0 .22em;
}

mjx-frac[type="d"] {
  vertical-align: .04em;
}

mjx-frac[delims] {
  padding: 0 .1em;
}

mjx-frac[atop] {
  padding: 0 .12em;
}

mjx-frac[atop][delims] {
  padding: 0;
}

mjx-dtable {
  display: inline-table;
  width: 100%;
}

mjx-dtable &gt; * {
  font-size: 2000%;
}

mjx-dbox {
  display: block;
  font-size: 5%;
}

mjx-num {
  display: block;
  text-align: center;
}

mjx-den {
  display: block;
  text-align: center;
}

mjx-mfrac[bevelled] &gt; mjx-num {
  display: inline-block;
}

mjx-mfrac[bevelled] &gt; mjx-den {
  display: inline-block;
}

mjx-den[align="right"], mjx-num[align="right"] {
  text-align: right;
}

mjx-den[align="left"], mjx-num[align="left"] {
  text-align: left;
}

mjx-nstrut {
  display: inline-block;
  height: .054em;
  width: 0;
  vertical-align: -.054em;
}

mjx-nstrut[type="d"] {
  height: .217em;
  vertical-align: -.217em;
}

mjx-dstrut {
  display: inline-block;
  height: .505em;
  width: 0;
}

mjx-dstrut[type="d"] {
  height: .726em;
}

mjx-line {
  display: block;
  box-sizing: border-box;
  min-height: 1px;
  height: .06em;
  border-top: .06em solid;
  margin: .06em -.1em;
  overflow: hidden;
}

mjx-line[type="d"] {
  margin: .18em -.1em;
}

mjx-mrow {
  display: inline-block;
  text-align: left;
}

mjx-msqrt {
  display: inline-block;
  text-align: left;
}

mjx-root {
  display: inline-block;
  white-space: nowrap;
}

mjx-surd {
  display: inline-block;
  vertical-align: top;
}

mjx-sqrt {
  display: inline-block;
  padding-top: .07em;
}

mjx-sqrt &gt; mjx-box {
  border-top: .07em solid;
}

mjx-sqrt.mjx-tall &gt; mjx-box {
  padding-left: .3em;
  margin-left: -.3em;
}

mjx-munderover {
  display: inline-block;
  text-align: left;
}

mjx-munderover:not([limits="false"]) {
  padding-top: .1em;
}

mjx-munderover:not([limits="false"]) &gt; * {
  display: block;
}

mjx-msubsup {
  display: inline-block;
  text-align: left;
}

mjx-script {
  display: inline-block;
  padding-right: .05em;
  padding-left: .033em;
}

mjx-script &gt; * {
  display: block;
}

mjx-TeXAtom {
  display: inline-block;
  text-align: left;
}

mjx-msub {
  display: inline-block;
  text-align: left;
}

mjx-c::before {
  display: block;
  width: 0;
}

.MJX-TEX {
  font-family: MJXZERO, MJXTEX;
}

.TEX-B {
  font-family: MJXZERO, MJXTEX-B;
}

.TEX-I {
  font-family: MJXZERO, MJXTEX-I;
}

.TEX-MI {
  font-family: MJXZERO, MJXTEX-MI;
}

.TEX-BI {
  font-family: MJXZERO, MJXTEX-BI;
}

.TEX-S1 {
  font-family: MJXZERO, MJXTEX-S1;
}

.TEX-S2 {
  font-family: MJXZERO, MJXTEX-S2;
}

.TEX-S3 {
  font-family: MJXZERO, MJXTEX-S3;
}

.TEX-S4 {
  font-family: MJXZERO, MJXTEX-S4;
}

.TEX-A {
  font-family: MJXZERO, MJXTEX-A;
}

.TEX-C {
  font-family: MJXZERO, MJXTEX-C;
}

.TEX-CB {
  font-family: MJXZERO, MJXTEX-CB;
}

.TEX-FR {
  font-family: MJXZERO, MJXTEX-FR;
}

.TEX-FRB {
  font-family: MJXZERO, MJXTEX-FRB;
}

.TEX-SS {
  font-family: MJXZERO, MJXTEX-SS;
}

.TEX-SSB {
  font-family: MJXZERO, MJXTEX-SSB;
}

.TEX-SSI {
  font-family: MJXZERO, MJXTEX-SSI;
}

.TEX-SC {
  font-family: MJXZERO, MJXTEX-SC;
}

.TEX-T {
  font-family: MJXZERO, MJXTEX-T;
}

.TEX-V {
  font-family: MJXZERO, MJXTEX-V;
}

.TEX-VB {
  font-family: MJXZERO, MJXTEX-VB;
}

mjx-stretchy-v mjx-c, mjx-stretchy-h mjx-c {
  font-family: MJXZERO, MJXTEX-S1, MJXTEX-S4, MJXTEX, MJXTEX-A ! important;
}

@font-face /* 0 */ {
  font-family: MJXZERO;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Zero.woff") format("woff");
}

@font-face /* 1 */ {
  font-family: MJXTEX;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff") format("woff");
}

@font-face /* 2 */ {
  font-family: MJXTEX-B;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff") format("woff");
}

@font-face /* 3 */ {
  font-family: MJXTEX-I;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff") format("woff");
}

@font-face /* 4 */ {
  font-family: MJXTEX-MI;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff") format("woff");
}

@font-face /* 5 */ {
  font-family: MJXTEX-BI;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff") format("woff");
}

@font-face /* 6 */ {
  font-family: MJXTEX-S1;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff") format("woff");
}

@font-face /* 7 */ {
  font-family: MJXTEX-S2;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff") format("woff");
}

@font-face /* 8 */ {
  font-family: MJXTEX-S3;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff") format("woff");
}

@font-face /* 9 */ {
  font-family: MJXTEX-S4;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff") format("woff");
}

@font-face /* 10 */ {
  font-family: MJXTEX-A;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff") format("woff");
}

@font-face /* 11 */ {
  font-family: MJXTEX-C;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff") format("woff");
}

@font-face /* 12 */ {
  font-family: MJXTEX-CB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff") format("woff");
}

@font-face /* 13 */ {
  font-family: MJXTEX-FR;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff") format("woff");
}

@font-face /* 14 */ {
  font-family: MJXTEX-FRB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff") format("woff");
}

@font-face /* 15 */ {
  font-family: MJXTEX-SS;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff") format("woff");
}

@font-face /* 16 */ {
  font-family: MJXTEX-SSB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff") format("woff");
}

@font-face /* 17 */ {
  font-family: MJXTEX-SSI;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff") format("woff");
}

@font-face /* 18 */ {
  font-family: MJXTEX-SC;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff") format("woff");
}

@font-face /* 19 */ {
  font-family: MJXTEX-T;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff") format("woff");
}

@font-face /* 20 */ {
  font-family: MJXTEX-V;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff") format("woff");
}

@font-face /* 21 */ {
  font-family: MJXTEX-VB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff") format("woff");
}

mjx-c.mjx-c1D44E.TEX-I::before {
  padding: 0.441em 0.529em 0.01em 0;
  content: "a";
}

mjx-c.mjx-c2212::before {
  padding: 0.583em 0.778em 0.082em 0;
  content: "\2212";
}

mjx-c.mjx-c32::before {
  padding: 0.666em 0.5em 0 0;
  content: "2";
}

mjx-c.mjx-c1D44F.TEX-I::before {
  padding: 0.694em 0.429em 0.011em 0;
  content: "b";
}

mjx-c.mjx-cB1::before {
  padding: 0.666em 0.778em 0 0;
  content: "\B1";
}

mjx-c.mjx-c221A::before {
  padding: 0.8em 0.853em 0.2em 0;
  content: "\221A";
}

mjx-c.mjx-c37::before {
  padding: 0.676em 0.5em 0.022em 0;
  content: "7";
}

mjx-c.mjx-c33::before {
  padding: 0.665em 0.5em 0.022em 0;
  content: "3";
}

mjx-c.mjx-c221E::before {
  padding: 0.442em 1em 0.011em 0;
  content: "\221E";
}

mjx-c.mjx-c2211.TEX-S2::before {
  padding: 0.95em 1.444em 0.45em 0;
  content: "\2211";
}

mjx-c.mjx-c1D45B.TEX-I::before {
  padding: 0.442em 0.6em 0.011em 0;
  content: "n";
}

mjx-c.mjx-c3D::before {
  padding: 0.583em 0.778em 0.082em 0;
  content: "=";
}

mjx-c.mjx-c31::before {
  padding: 0.666em 0.5em 0 0;
  content: "1";
}

mjx-c.mjx-c1D452.TEX-I::before {
  padding: 0.442em 0.466em 0.011em 0;
  content: "e";
}

mjx-c.mjx-c1D456.TEX-I::before {
  padding: 0.661em 0.345em 0.011em 0;
  content: "i";
}

mjx-c.mjx-c1D70B.TEX-I::before {
  padding: 0.431em 0.57em 0.011em 0;
  content: "\3C0";
}

mjx-c.mjx-c2B::before {
  padding: 0.583em 0.778em 0.082em 0;
  content: "+";
}

mjx-c.mjx-c28::before {
  padding: 0.75em 0.389em 0.25em 0;
  content: "(";
}

mjx-c.mjx-c35::before {
  padding: 0.666em 0.5em 0.022em 0;
  content: "5";
}

mjx-c.mjx-c1D465.TEX-I::before {
  padding: 0.442em 0.572em 0.011em 0;
  content: "x";
}

mjx-c.mjx-c34::before {
  padding: 0.677em 0.5em 0 0;
  content: "4";
}

mjx-c.mjx-c29::before {
  padding: 0.75em 0.389em 0.25em 0;
  content: ")";
}

mjx-c.mjx-c1D6FC.TEX-I::before {
  padding: 0.442em 0.64em 0.011em 0;
  content: "\3B1";
}

mjx-c.mjx-c1D461.TEX-I::before {
  padding: 0.626em 0.361em 0.011em 0;
  content: "t";
}

mjx-c.mjx-c1D443.TEX-I::before {
  padding: 0.683em 0.751em 0 0;
  content: "P";
}

mjx-c.mjx-c1D442.TEX-I::before {
  padding: 0.704em 0.763em 0.022em 0;
  content: "O";
}

mjx-c.mjx-c2C::before {
  padding: 0.121em 0.278em 0.194em 0;
  content: ",";
}

mjx-c.mjx-c2026::before {
  padding: 0.12em 1.172em 0 0;
  content: "\2026";
}

mjx-c.mjx-c1D45E.TEX-I::before {
  padding: 0.442em 0.46em 0.194em 0;
  content: "q";
}

mjx-c.mjx-c1D446.TEX-I::before {
  padding: 0.705em 0.645em 0.022em 0;
  content: "S";
}

mjx-c.mjx-c1D706.TEX-I::before {
  padding: 0.694em 0.583em 0.012em 0;
  content: "\3BB";
}

mjx-c.mjx-c1D458.TEX-I::before {
  padding: 0.694em 0.521em 0.011em 0;
  content: "k";
}

mjx-c.mjx-c222B.TEX-S2::before {
  padding: 1.36em 0.944em 0.862em 0;
  content: "\222B";
}

mjx-c.mjx-c30::before {
  padding: 0.666em 0.5em 0.022em 0;
  content: "0";
}

mjx-c.mjx-c1D451.TEX-I::before {
  padding: 0.694em 0.52em 0.01em 0;
  content: "d";
}

mjx-c.mjx-c1D438.TEX-I::before {
  padding: 0.68em 0.764em 0 0;
  content: "E";
}

mjx-c.mjx-c1D45A.TEX-I::before {
  padding: 0.442em 0.878em 0.011em 0;
  content: "m";
}

mjx-c.mjx-c1D450.TEX-I::before {
  padding: 0.442em 0.433em 0.011em 0;
  content: "c";
}
&lt;/style&gt;&lt;/head&gt;
&lt;body&gt;
&lt;div class="document" id="formula"&gt;
&lt;h1 class="title"&gt;Formula&lt;/h1&gt;

&lt;p&gt;These should be some in line math &lt;span class="math"&gt;&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" tabindex="0" ctxtmenu_counter="0" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math class="MJX-TEX" aria-hidden="true"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D44E TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c2212"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mn class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D44F TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.363em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="inline"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mo&gt;âˆ’&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;&lt;/span&gt; here.&lt;/p&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="1" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-mfrac&gt;&lt;mjx-frac type="d"&gt;&lt;mjx-num&gt;&lt;mjx-nstrut type="d"&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mrow&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-cB1"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msqrt space="3"&gt;&lt;mjx-sqrt&gt;&lt;mjx-surd&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c221A"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;/mjx-surd&gt;&lt;mjx-box style="padding-top: 0.158em;"&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c37"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-box&gt;&lt;/mjx-sqrt&gt;&lt;/mjx-msqrt&gt;&lt;/mjx-mrow&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line type="d"&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut type="d"&gt;&lt;/mjx-dstrut&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c33"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mo&gt;Â±&lt;/mo&gt;&lt;msqrt&gt;&lt;mn&gt;7&lt;/mn&gt;&lt;/msqrt&gt;&lt;/mrow&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/mfrac&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;p&gt;Some text here, and then some more math:&lt;/p&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="2" style="font-size: 123.1%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-munderover&gt;&lt;mjx-over style="padding-bottom: 0.192em; padding-left: 0.368em;"&gt;&lt;mjx-mi class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c221E"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-over&gt;&lt;mjx-box&gt;&lt;mjx-munder&gt;&lt;mjx-row&gt;&lt;mjx-base&gt;&lt;mjx-mo class="mjx-lop"&gt;&lt;mjx-c class="mjx-c2211 TEX-S2"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;/mjx-base&gt;&lt;/mjx-row&gt;&lt;mjx-row&gt;&lt;mjx-under style="padding-top: 0.167em; padding-left: 0.058em;"&gt;&lt;mjx-texatom size="s" texclass="ORD"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D45B TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-texatom&gt;&lt;/mjx-under&gt;&lt;/mjx-row&gt;&lt;/mjx-munder&gt;&lt;/mjx-box&gt;&lt;/mjx-munderover&gt;&lt;mjx-mfrac space="2"&gt;&lt;mjx-frac type="d"&gt;&lt;mjx-num&gt;&lt;mjx-nstrut type="d"&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mrow&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2212"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D452 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.363em;"&gt;&lt;mjx-texatom size="s" texclass="ORD"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D456 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D70B TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-texatom&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-mrow&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line type="d"&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut type="d"&gt;&lt;/mjx-dstrut&gt;&lt;mjx-msup&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-script style="vertical-align: 0.289em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D45B TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c2B"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c28"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mfrac&gt;&lt;mjx-frac type="d"&gt;&lt;mjx-num&gt;&lt;mjx-nstrut type="d"&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mrow&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c35"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c2212"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mfrac space="3"&gt;&lt;mjx-frac&gt;&lt;mjx-num&gt;&lt;mjx-nstrut&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut&gt;&lt;/mjx-dstrut&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;/mjx-mrow&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line type="d"&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut type="d"&gt;&lt;/mjx-dstrut&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c34"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c29"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;munderover&gt;&lt;mo data-mjx-texclass="OP"&gt;âˆ‘&lt;/mo&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;mi mathvariant="normal"&gt;âˆž&lt;/mi&gt;&lt;/munderover&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mo&gt;âˆ’&lt;/mo&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;Ï€&lt;/mi&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msup&gt;&lt;/mfrac&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mn&gt;5&lt;/mn&gt;&lt;mo&gt;âˆ’&lt;/mo&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;mn&gt;4&lt;/mn&gt;&lt;/mfrac&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;p&gt;To get this to work right with rst2pdf, we need to put a blank line between each line of math.&lt;/p&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="3" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-msub&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D6FC TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D461 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c28"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D456 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c29"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mi class="mjx-i" space="4"&gt;&lt;mjx-c class="mjx-c1D443 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c28"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D442 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2C"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="2"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D442 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2C"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="2"&gt;&lt;mjx-c class="mjx-c2026"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="2"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D442 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D461 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2C"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="2"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D45E TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em; margin-left: -0.014em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D461 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c29"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="4"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D446 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em; margin-left: -0.032em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D456 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D706 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;msub&gt;&lt;mi&gt;Î±&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;P&lt;/mi&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;mo&gt;â€¦&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;q&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;S&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mi&gt;Î»&lt;/mi&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="4" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D458 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msubsup space="4"&gt;&lt;mjx-mo class="mjx-lop"&gt;&lt;mjx-c class="mjx-c222B TEX-S2"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-script style="vertical-align: -0.896em; margin-left: -0.388em;"&gt;&lt;mjx-texatom size="s" texclass="ORD" style="margin-left: 0.647em;"&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;mjx-c class="mjx-c30"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-texatom&gt;&lt;mjx-spacer style="margin-top: 1.498em;"&gt;&lt;/mjx-spacer&gt;&lt;mjx-texatom size="s" texclass="ORD"&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c30"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-texatom&gt;&lt;/mjx-script&gt;&lt;/mjx-msubsup&gt;&lt;mjx-mi class="mjx-i" space="2"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D451 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;msubsup&gt;&lt;mo data-mjx-texclass="OP"&gt;âˆ«&lt;/mo&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mn&gt;10&lt;/mn&gt;&lt;/mrow&gt;&lt;/msubsup&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="5" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D438 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mi class="mjx-i" space="4"&gt;&lt;mjx-c class="mjx-c1D45A TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D450 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.413em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;msup&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;p&gt;More inline math: &lt;span class="math"&gt;&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" tabindex="0" ctxtmenu_counter="6" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math class="MJX-TEX" aria-hidden="true"&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D452 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.363em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="inline"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;


&lt;/body&gt;&lt;span style="--colorFg:#d3d9e3; --colorFgAlpha:#d3d9e31a; --colorFgIntense:#f9ffff; --colorFgFaded:#c9ced8; --colorFgFadedMore:#bec3cd; --colorFgFadedMost:#a6abb3; --colorBg:#2e2f37; --colorBgAlpha:#2e2f37e6; --colorBgAlphaHeavy:#2e2f37a6; --colorBgAlphaHeavier:#2e2f3740; --colorBgAlphaBlur:#2e2f37f2; --colorBgDark:#282931; --colorBgDarker:#23242b; --colorBgLight:#32333b; --colorBgLighter:#42434b; --colorBgLightIntense:#36373f; --colorBgIntense:#23242b; --colorBgIntenser:#1b1c24; --colorBgIntserAlpha:#181920eb; --colorBgInverse:#32333b; --colorBgInverser:#3e3f47; --colorBgFaded:#3a3b43; --backgroundBlur:blur(10px); --colorHighlightBg:#6590fd; --colorHighlightBgUnfocused:#565860; --colorHighlightBgAlpha:#6590fd1a; --colorHighlightBgDark:#4779e3; --colorHighlightFg:#ffffff; --colorHighlightFgAlpha:#ffffff80; --colorHighlightFgAlphaHeavy:#ffffff40; --colorAccentBg:#44454d; --colorAccentBgAlpha:#78798266; --colorAccentBgAlphaHeavy:#78798240; --colorAccentBgDark:#34353d; --colorAccentBgDarker:#1d1e25; --colorAccentBgFaded:#393939; --colorAccentBgFadedMore:#595a62; --colorAccentBgFadedMost:#787982; --colorAccentBorder:#34353d; --colorAccentBorderDark:#2a2b33; --colorAccentFg:#ffffff; --colorAccentFgFaded:#cacacb; --colorAccentFgAlpha:#ffffff40; --colorBorder:#1b1c24; --colorBorderDisabled:#23242b; --colorBorderSubtle:#212229; --colorBorderIntense:#0b0c15; --colorSuccessBg:#06a700; --colorSuccessBgAlpha:#06a7001a; --colorSuccessFg:#ffffff; --colorWarningBg:#efaf00; --colorWarningBgAlpha:#efaf001a; --colorWarningFg:#000000; --colorErrorBg:#c64539; --colorErrorBgAlpha:#c645391a; --colorErrorFg:#ffffff;"&gt;&lt;/span&gt;&lt;/html&gt;</t>
<t tx="ekr.20241206101317.35" __bookmarks="7d7100580700000069735f6475706571014930300a732e">@language python
@nosearch
# changed
</t>
<t tx="ekr.20241206101317.36"></t>
<t tx="ekr.20241206101317.37" __bookmarks="7d7100580700000069735f6475706571014930300a732e">@language html
&lt;a href="http://127.0.0.1:8000/leo_toc.html"&gt;Link&lt;/a&gt;</t>
<t tx="ekr.20241206101317.38">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;style&gt;
ul.leo-tree-example  {
    background-color: #ffffec;
    zoom: 150%; # blurs icons a bit.
}
ul.leo-tree-example li {
    background-repeat: no-repeat;
    background-position: 0px 5px;
    padding-left: 27px;
}
li {
    background-image:
url('https://raw.github.com/vivainio/leo/master/leo/Icons/box00.GIF');
    background-repeat: no-repeat;
    background-position: 0px 5px;
    padding-left: 27px;
}
li.selected {
    background-color: lightgrey;
}
li.leaf {
    list-style-type: none;
}
li.plus {
  list-style-image: url('http://leoeditor.com/plusnode.gif')
}
li.minus {
  list-style-image: url('http://leoeditor.com/minusnode.gif')
}
li.leaf {
  background-image: url('http://leoeditor.com/box00.GIF')
}
li.body {
  background-image: url('http://leoeditor.com/box01.GIF')
}
li.mark {
  background-image: url('http://leoeditor.com/box02.GIF')
}
li.mark-body {
  background-image: url('http://leoeditor.com/box03.GIF')
}
li.clone {
  background-image: url('http://leoeditor.com/box04.GIF')
}
li.clone-body {
  background-image: url('http://leoeditor.com/box05.GIF')
}
li.clone-mark {
  background-image: url('http://leoeditor.com/box06.GIF')
}
li.clone-mark-body {
  background-image: url('http://leoeditor.com/box07.GIF')
}
li.dirty {
  background-image: url('http://leoeditor.com/box08.GIF')
}
li.dirty-body {
  background-image: url('http://leoeditor.com/box09.GIF')
}
li.dirty-mark {
  background-image: url('http://leoeditor.com/box10.GIF')
}
li.dirty-mark-body {
  background-image: url('http://leoeditor.com/box11.GIF')
}
li.dirty-clone {
  background-image: url('http://leoeditor.com/box12.GIF')
}
li.dirty-clone-body {
  background-image: url('http://leoeditor.com/box13.GIF')
}
li.dirty-clone-mark {
  background-image: url('http://leoeditor.com/box14.GIF')
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;ul class="leo-tree-example"&gt;
&lt;li class='plus clone-mark'&gt;
test
&lt;/li&gt;
&lt;ul&gt;
  &lt;li class='plus clone-body'&gt;
  child
  &lt;/li&gt;
  &lt;ul&gt;
    &lt;li class='leaf body'&gt;
    grandchild
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;li class='plus clone-body'&gt;
  child
  &lt;/li&gt;
  &lt;ul&gt;
    &lt;li class='leaf body'&gt;
    grandchild
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;</t>
<t tx="ekr.20241206101317.39">@language html

&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;!--@+leo-ver=5-thin--&gt;
&lt;!--@+node:ekr.20100808060203.4273: * @thin html/front.html--&gt;
&lt;!--@@first--&gt;
&lt;!--@@first--&gt;

&lt;!--@@language html--&gt;
&lt;!--@@tabwidth -2--&gt;

&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;
&lt;head&gt;
    &lt;!--@+&lt;&lt; head &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4274: ** &lt;&lt; head &gt;&gt;--&gt;
    &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;

    &lt;title&gt;Leo's Home Page&lt;/title&gt;
    &lt;link rel="stylesheet" href="_static/default.css" type="text/css" /&gt;
    &lt;link rel="stylesheet" href="_static/silver_city.css" type="text/css" /&gt;
    &lt;!--
    &lt;script type="text/javascript"&gt;
      var DOCUMENTATION_OPTIONS = {
        URL_ROOT:    '',
        VERSION:     '4.7-final',
        COLLAPSE_MODINDEX: false,
        FILE_SUFFIX: '.html',
        HAS_SOURCE:  false
      };
    &lt;/script&gt;
    --&gt;
    &lt;!-- &lt;script type="text/javascript" src="_static/jquery.js"&gt;&lt;/script&gt;--&gt;
    &lt;!-- &lt;script type="text/javascript" src="_static/doctools.js"&gt;&lt;/script&gt;--&gt;
    &lt;link rel="top" title="Leo v4.7-final documentation" href="index.html" /&gt;
    &lt;link rel="Users Guide" title="Leo&amp;#8217;s Users Guide" href="leo_toc.html" /&gt;
    &lt;!-- &lt;link rel="prev" title="Chapter 5: Using LeoÃ¢â‚¬â„¢s Commands" href="commands.html" /&gt;--&gt; 
    &lt;!--@-&lt;&lt; head &gt;&gt;--&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;!--@+&lt;&lt; div top nav &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4276: ** &lt;&lt; div top nav &gt;&gt;--&gt;
    &lt;div class="related"&gt;
      &lt;h3&gt;Navigation&lt;/h3&gt;
      &lt;ul&gt;
        &lt;li class="right" style="margin-right: 10px"&gt;
          &lt;a href="genindex.html" title="General Index"
             accesskey="I"&gt;index&lt;/a&gt;&lt;/li&gt;
        &lt;li class="right" &gt;
          &lt;a href="leo_toc.html" title="Leo&amp;#8217;s Users Guide"
             accesskey="N"&gt;contents&lt;/a&gt; |&lt;/li&gt;
        &lt;!--
        &lt;li class="right" &gt;
          &lt;a href="commands.html" title="Chapter 5: Using Leo&amp;#8217;s Commands"
             accesskey="P"&gt;previous&lt;/a&gt; |&lt;/li&gt;
        --&gt;
        &lt;li&gt;&lt;a href="leo_toc.html"&gt;Leo v4.7-final documentation&lt;/a&gt; &amp;raquo;&lt;/li&gt;

      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!--@-&lt;&lt; div top nav &gt;&gt;--&gt;
    &lt;div class="document"&gt;
      &lt;div class="documentwrapper"&gt;
        &lt;div class="bodywrapper"&gt;
          &lt;div class="body"&gt;
            &lt;!--@+&lt;&lt; div outer section &gt;&gt;--&gt;
            &lt;!--@+node:ekr.20100808060203.4286: ** &lt;&lt; div outer section &gt;&gt;--&gt;
            &lt;div class="section" id="Leo&amp;#8217;s Home page"&gt;

                &lt;h1&gt;Leo&amp;#8217;s Home Page&lt;/h1&gt;

                Leo is...

                &lt;!--@+&lt;&lt; what is Leo list &gt;&gt;--&gt;
                &lt;!--@+node:ekr.20100808060203.4288: *3* &lt;&lt; what is Leo list &gt;&gt;--&gt;
                &lt;li&gt;
                    An outline-oriented &lt;i&gt;browser&lt;/i&gt; and &lt;i&gt;project manager&lt;/i&gt; that organizes&lt;br&gt;
                    programs, web sites, URL's, pictures, movies or any other kind of data.&lt;br&gt;
                    You can organize your data in as many ways as you like within a &lt;i&gt;single&lt;/i&gt; outline.&lt;/li&gt;
                &lt;li&gt;
                    A &lt;i&gt;programmer's editor&lt;/i&gt; that fully integrates outline structure with programming.
                    &lt;!--
                    &lt;a HREF="http://www.eecs.harvard.edu/~nr/noweb/"&gt;noweb&lt;/a&gt; and
                    &lt;a HREF="http://www-cs-faculty.stanford.edu/~knuth/cweb.html"&gt;CWEB&lt;/a&gt; markup.&lt;/li&gt;
                    --&gt;
                &lt;li&gt;
                    &lt;i&gt;Fully scriptable&lt;/i&gt; using &lt;a HREF="http://www.python.org/"&gt;Python&lt;/a&gt;.
                    Leo's core is 100% pure Python.&lt;/li&gt;
                &lt;li&gt;
                    &lt;i&gt;Portable&lt;/i&gt;. Leo runs on Windows, Linux and MacOS X.&lt;br&gt;
                    Leo requires either the
                    &lt;a HREF="http://docs.python.org/library/tkinter.html"&gt;Tkinter&lt;/a&gt; or
                    &lt;a HREF="http://www.riverbankcomputing.co.uk/software/pyqt/intro"&gt;PyQt&lt;/a&gt; widget set.&lt;br&gt;
                    Leo's outline files are &lt;a HREF="http://www.w3.org/XML/"&gt;XML&lt;/a&gt; format.&lt;/li&gt;
                &lt;li&gt;
                    &lt;a href="http://www.opensource.org/"&gt;&lt;i&gt;Open Software&lt;/i&gt;&lt;/a&gt;,
                    distributed under the
                    &lt;a href="http://www.opensource.org/licenses/mit-license.php/"&gt;MIT License&lt;/a&gt;.&lt;/li&gt;
                &lt;!--@-&lt;&lt; what is Leo list &gt;&gt;--&gt;
                &lt;!-- div body toc --&gt;
                &lt;!-- &lt; &lt; inner sections &gt; &gt; --&gt;

                &lt;p&gt;Leo has an active community of helpful users and developers.&lt;br&gt;
                Please use the &lt;a href="http://groups.google.com/group/leo-editor"&gt;leo-editor&lt;/a&gt;
                group to ask questions and make suggestions.&lt;/p&gt;

                &lt;p&gt;Leo's author is &lt;a HREF="ekr.html"&gt;Edward K. Ream&lt;/a&gt;&lt;/p&gt;

            &lt;/div&gt;
            &lt;!--@-&lt;&lt; div outer section &gt;&gt;--&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;!--@+&lt;&lt; sidebar &gt;&gt;--&gt;
      &lt;!--@+node:ekr.20100808060203.4282: ** &lt;&lt; sidebar &gt;&gt;--&gt;
      &lt;div class="sphinxsidebar"&gt;
          &lt;div class="sphinxsidebarwrapper"&gt;
              &lt;!--@+&lt;&lt; sidebar contents &gt;&gt;--&gt;
              &lt;!--@+node:ekr.20100808060203.4283: *3* &lt;&lt; sidebar contents &gt;&gt;--&gt;
              &lt;p class="logo"&gt;&lt;a href="leo_toc.html"&gt;
                &lt;img class="logo" src="_static/Leo4-80-border.jpg" alt="Logo"/&gt;&lt;/a&gt;&lt;/p&gt;

              &lt;a class="reference external" href="intro.html"&gt;
                Read this tutorial first&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm"&gt;
                Another tutorial&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http:screen-shots.html"&gt;
                Screen shots of Leo&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="testimonials.html"&gt;
                Quotes from Leo&amp;#8217;s Users&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external"
                href="http://sourceforge.net/project/showfiles.php?group_id=3458&amp;amp;package_id=29106"&gt;
                Download Leo&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://groups.google.com/group/leo-editor"&gt;
                leo-editor: Google Groups&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://launchpad.net/leo-editor"&gt;
                leo-editor: Launchpad&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://leo.zwiki.org"&gt;
                Leo&amp;#8217;s Wiki&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://www.mind-mapping.org/"&gt;
                Mind Mapping&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="leoLinks.html"&gt;
                More links...&lt;/a&gt;&lt;br&gt;

              &lt;!-- &lt;a class="reference external"  href="install.html"&gt;Installing Leo&lt;/a&gt;&lt;br&gt;--&gt; 
              &lt;!-- &lt;a class="reference external"  href="intro.html"&gt;Beginners Guide&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!-- &lt;a class="reference external"  href="leo_toc.html"&gt;Users Guide&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!-- &lt;a class="reference external"  href="FAQ.html"&gt;FAQ&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!--Leo&amp;#8217;s &lt;a class="reference external" href="http://www.greygreen.org/leo/"&gt;Daily snapshots&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!-- 
              &lt;h5&gt;Edward K. Ream&lt;/h5&gt;
              &lt;a class="reference external" href="ekr.html"&gt;Home&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="mailto:edreamleo@gmail.com"&gt;Contact&lt;/a&gt;&lt;br&gt;
              --&gt; 
              &lt;!--@-&lt;&lt; sidebar contents &gt;&gt;--&gt;
          &lt;!-- &lt; &lt; sidebar links &gt; &gt; --&gt;
          &lt;!-- &lt; &lt; searchbox &gt; &gt; --&gt;
          &lt;/div&gt;
      &lt;/div&gt;
      &lt;!--@-&lt;&lt; sidebar &gt;&gt;--&gt;
      &lt;div class="clearer"&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;!--@+&lt;&lt; div bottom nav &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4277: ** &lt;&lt; div bottom nav &gt;&gt;--&gt;
    &lt;div class="related"&gt;
      &lt;h3&gt;Navigation&lt;/h3&gt;
      &lt;ul&gt;
        &lt;li class="right" style="margin-right: 10px"&gt;
          &lt;a href="genindex.html" title="General Index"
             &gt;index&lt;/a&gt;&lt;/li&gt;
        &lt;li class="right" &gt;
          &lt;a href="leo_toc.html" title="Leo&amp;#8217;s Users Guide"
             &gt;contents&lt;/a&gt; |&lt;/li&gt;
        &lt;!--
        &lt;li class="right" &gt;
          &lt;a href="commands.html" title="Chapter 5: Using Leo&amp;#8217;s Commands"
             &gt;previous&lt;/a&gt; |&lt;/li&gt;
        --&gt;
        &lt;li&gt;&lt;a href="leo_toc.html"&gt;Leo v4.7-final documentation&lt;/a&gt; &amp;raquo;&lt;/li&gt; 
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!--@-&lt;&lt; div bottom nav &gt;&gt;--&gt;
    &lt;!--@+&lt;&lt; div footer &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4278: ** &lt;&lt; div footer &gt;&gt;--&gt;
    &lt;div class="footer"&gt;
      &amp;copy; Copyright 2010, Edward K. Ream.
      Last updated on Aug 08, 2010.
      Created using &lt;a href="http://sphinx.pocoo.org/"&gt;Sphinx&lt;/a&gt; 0.6.3.
    &lt;/div&gt;
    &lt;!--@-&lt;&lt; div footer &gt;&gt;--&gt;
  &lt;/body&gt;
&lt;/html&gt;
&lt;!--@-leo--&gt;
</t>
<t tx="ekr.20241206101317.4">from leo.core.leoQt import QtGui
import leo.plugins.demo as demo_module
import imp
imp.reload(demo_module)</t>
<t tx="ekr.20241206101317.40"></t>
<t tx="ekr.20241206101317.41">C:\Repos\leo-editor\leo\Icons\LeoLogo.svg

@language rest
@wrap
</t>
<t tx="ekr.20241206101317.42"></t>
<t tx="ekr.20241206101317.43">Leosplash.GIF
</t>
<t tx="ekr.20241206101317.44"></t>
<t tx="ekr.20241206101317.45">#
# a bar plot with errorbars
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N)  # the x locations for the groups
width = 0.35       # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, menMeans, width, color='r', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, womenMeans, width, color='y', yerr=womenStd)
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))

def autolabel(rects):
    # attach some text labels
    for rect in rects:
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
                '%d' % int(height),
                ha='center', va='bottom')

autolabel(rects1)
autolabel(rects2)
</t>
<t tx="ekr.20241206101317.46"># http://matplotlib.org/1.5.1/examples/animation/basic_example.html

def update_line(num, data, line):
        line.set_data(data[..., :num])
        return line, # a tuple.

fig1 = plt.figure()
data = np.random.rand(2, 100) ### 2, 25)
# g.printObj(data)
l, = plt.plot([], [], 'r-')
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, 25,
    fargs=(data, l), interval=50, blit=True)
# Retain a reference to animation.
g.app.permanentScriptDict['pyplot_animations'] = animation

</t>
<t tx="ekr.20241206101317.47"># http://matplotlib.org/1.5.1/examples/animation/basic_example.html

fig2 = plt.figure()
x = np.arange(-9, 10)
y = np.arange(-9, 10).reshape(-1, 1)
base = np.hypot(x, y)
images = []
for add in np.arange(15):
    images.append((plt.pcolor(x, y, base + add, norm=plt.Normalize(0, 30)),))
animation = animation.ArtistAnimation(fig2, images,
    interval=50,
    repeat_delay=30,
    blit=True)
# Retain a reference to animation.
g.app.permanentScriptDict['pyplot_animations'] = animation
</t>
<t tx="ekr.20241206101317.48"># http://matplotlib.org/1.5.1/examples/animation/animate_decay.html
# import numpy as np
# import matplotlib.pyplot as plt
# import matplotlib.animation as animation

@others

# global data.
fig, ax = plt.subplots()
line, = ax.plot([], [], lw=2)
ax.grid()
xdata, ydata = [], []
animation = animation.FuncAnimation(
    fig, run, data_gen, blit=False, interval=10,
    repeat=False, init_func=init,
    cache_frame_data=False,
)
# Retain a reference to animation.
g.app.permanentScriptDict['pyplot_animations'] = animation
</t>
<t tx="ekr.20241206101317.49">def data_gen(t=0):
    for n in range(500): # n not used.
        t += 0.1
        yield t, np.sin(2*np.pi*t) * np.exp(-t/10.0)
</t>
<t tx="ekr.20241206101317.5">class MyDemo (demo_module.Demo):
    
    @others
</t>
<t tx="ekr.20241206101317.50">def init():
    ax.set_ylim(-1.1, 1.1)
    ax.set_xlim(0, 10)
    del xdata[:]
    del ydata[:]
    line.set_data(xdata, ydata)
    return line,
</t>
<t tx="ekr.20241206101317.51">def run(data):
    # update the data
    t, y = data
    xdata.append(t)
    ydata.append(y)
    xmin, xmax = ax.get_xlim()
    if t &gt;= xmax:
        ax.set_xlim(xmin, 2*xmax)
        ax.figure.canvas.draw()
    line.set_data(xdata, ydata)
    return line,
</t>
<t tx="ekr.20241206101317.52">x1 = np.linspace(0.0, 5.0)
x2 = np.linspace(0.0, 2.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
y2 = np.cos(2 * np.pi * x2)
# Grpah 1.
plt.subplot(2, 1, 1)
plt.plot(x1, y1, 'ko-')
plt.title('A tale of 2 subplots')
plt.ylabel('Damped oscillation')
# Graph 2.
plt.subplot(2, 1, 2)
plt.plot(x2, y2, 'r.-')
plt.xlabel('time (s)')
plt.ylabel('Undamped')</t>
<t tx="ekr.20241206101317.53"></t>
<t tx="ekr.20241206101317.54">@nocolor-node
&lt;?xml version="1.0" standalone="no"?&gt;
&lt;!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"&gt;
&lt;svg width="20cm" height="15cm" viewBox="0 0 800 600"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlik/"
     baseProfile="tiny" version="1.2"&gt;
   &lt;title&gt;Spheres&lt;/title&gt;
   &lt;desc&gt;Semi-transparent bubbles on a colored background.&lt;/desc&gt;
     &lt;defs&gt;
       &lt;!-- Create radial gradients for each bubble. --&gt;
       &lt;radialGradient id="blueBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#cdcdff" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#cdaacd" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="redBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#ffcdcd" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#bbbb99" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="greenBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#cdffcd" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#99aaaa" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="yellowBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#ffffcd" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#bbbbaa" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="background" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="400" fx="250" fy="250"&gt;
         &lt;stop offset="0%" stop-color="#ffffee" /&gt;
         &lt;stop offset="100%" stop-color="#ccccaa" /&gt;
       &lt;/radialGradient&gt;
       &lt;linearGradient id="surface" gradientUnits="userSpaceOnUse"
                       x1="-100" y1="200" x2="400" y2="200"&gt;
         &lt;stop offset="0%" stop-color="#ffffcc" /&gt;
         &lt;stop offset="100%" stop-color="#bbbb88" /&gt;
       &lt;/linearGradient&gt;

       &lt;!-- Create radial gradients for each circle to make them look like
            spheres. --&gt;
       &lt;radialGradient id="blueSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="blue" /&gt;
         &lt;stop offset="100%" stop-color="#222244" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="redSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="red" /&gt;
         &lt;stop offset="100%" stop-color="#442222" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="greenSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="green" /&gt;
         &lt;stop offset="100%" stop-color="#113311" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="yellowSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="yellow" /&gt;
         &lt;stop offset="100%" stop-color="#444422" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="shadowGrad" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="50"&gt;
         &lt;stop offset="0%" stop-color="black" stop-opacity="1.0" /&gt;
         &lt;stop offset="100%" stop-color="black" stop-opacity="0.0" /&gt;
       &lt;/radialGradient&gt;

       &lt;!-- Define a shadow for each sphere. --&gt;
       &lt;circle id="shadow" fill="url(#shadowGrad)" cx="0" cy="0" r="100" /&gt;
       &lt;g id="bubble"&gt;
         &lt;circle fill="black" cx="0" cy="0" r="50" /&gt;
         &lt;circle fill="#a6ce39" cx="0" cy="0" r="33" /&gt;
         &lt;path fill="black" d="M 37,50 L 50,37 L 12,-1 L 22,-11 L 10,-24 L -24,10
                               L -11,22 L -1,12 Z" /&gt;
         &lt;circle cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/defs&gt;
   &lt;g&gt;
     &lt;rect fill="url(#background)" x="0" y="0" width="800" height="600" /&gt;
   &lt;/g&gt;

   &lt;g transform="translate(200,700)"&gt;
     &lt;use xlink:href="#bubble" fill="url(#blueBubble)" /&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="1s" dur="10s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(315,700)"&gt;
     &lt;g transform="scale(0.5,0.5)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#redBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="3s" dur="7s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(80,700)"&gt;
     &lt;g transform="scale(0.65,0.65)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#greenBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="5s" dur="9s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(255,700)"&gt;
     &lt;g transform="scale(0.3,0.3)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#yellowBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="2s" dur="6s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(565,700)"&gt;
     &lt;g transform="scale(0.4,0.4)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#blueBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="4s" dur="8s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(715,700)"&gt;
     &lt;g transform="scale(0.6,0.6)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#redBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="1s" dur="4s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(645,700)"&gt;
     &lt;g transform="scale(0.375,0.375)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#greenBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="0s" dur="11s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(555,700)"&gt;
     &lt;g transform="scale(0.9,0.9)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#yellowBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="3s" dur="7.5s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;

   &lt;g transform="translate(360,700)"&gt;
     &lt;g transform="scale(0.5,0.5)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#blueBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="3s" dur="6s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(215,700)"&gt;
     &lt;g transform="scale(0.45,0.45)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#redBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="5.5s" dur="7s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(420,700)"&gt;
     &lt;g transform="scale(0.75,0.75)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#greenBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="1s" dur="9s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(815,700)"&gt;
     &lt;g transform="scale(0.6,0.6)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#yellowBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="2s" dur="9.5s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;

   &lt;g transform="translate(225,375)" &gt;
     &lt;g transform="scale(1.0,0.5)" &gt;
       &lt;path d="M 0 0 L 350 0 L 450 450 L -100 450 z"
             fill="url(#surface)" stroke="none" /&gt;
     &lt;/g&gt;
   &lt;/g&gt;
     &lt;g transform="translate(200,0)" &gt;
     &lt;g transform="translate(200,490) scale(2.0,1.0) rotate(45)" &gt;
       &lt;rect fill="#a6ce39" x="-69" y="-69" width="138" height="138" /&gt;
       &lt;circle fill="black" cx="0" cy="0" r="50" /&gt;
       &lt;circle fill="#a6ce39" cx="0" cy="0" r="33" /&gt;
       &lt;path fill="black" d="M 37,50 L 50,37 L 12,-1 L 22,-11 L 10,-24 L -24,10
                             L -11,22 L -1,12 Z" /&gt;
       &lt;animateTransform attributeName="transform"  type="rotate" additive="sum" values="0; 360"
                  begin="0s" dur="10s" fill="freeze" repeatCount="indefinite" /&gt;
     &lt;/g&gt;
     &lt;g transform="translate(200,375)"&gt;
       &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
       &lt;circle fill="url(#blueSphere)" cx="0" cy="0" r="100" /&gt;
     &lt;/g&gt;
     &lt;g transform="translate(315,440)"&gt;
       &lt;g transform="scale(0.5,0.5)"&gt;
         &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
         &lt;circle fill="url(#redSphere)" cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/g&gt;
     &lt;g transform="translate(80,475)"&gt;
       &lt;g transform="scale(0.65,0.65)"&gt;
         &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
         &lt;circle fill="url(#greenSphere)" cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/g&gt;
     &lt;g transform="translate(255,525)"&gt;
       &lt;g transform="scale(0.3,0.3)"&gt;
         &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
         &lt;circle fill="url(#yellowSphere)" cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/g&gt;
   &lt;/g&gt;
&lt;/svg&gt;</t>
<t tx="ekr.20241206101317.55">C:\Repos\leo-editor\leo\Icons\bubbles.svg
</t>
<t tx="ekr.20241206101317.56">@language rest
@wrap

''' Creates a window for *live* rendering of rst, html, etc.  (Qt only).

Commands
========

viewrendered.py creates the following (``Alt-X``) commands:

``viewrendered``
    opens a new window where the current body text is rendered as HTML
    (if it starts with '&lt;'), or otherwise reStructuredText.
``viewrendered-big``
    as above, but zoomed in, useful for presentations
``viewrendered-html``
    displays the html source generated from reStructuredText, useful for
    debugging

``viewrendered`` sets the process current directory (os.chdir()) to the path
to the node being rendered, to allow relative paths to work in
``.. image::`` directives.

reStructuredText errors and warnings may be shown.  For example, both::

    Heading
    -------

    `This` is **really** a line of text.

and::

    &lt;h1&gt;Heading&lt;h1&gt;

    &lt;tt&gt;This&lt;/tt&gt; is &lt;b&gt;really&lt;/b&gt; a line of text.

will look something like:

**Heading**

`This` is **really** a line of text.

Settings
========

@string view-rendered-default-kind = rst
----------------------------------------

The default kind of rendering.  One of (big,rst,html)
    
@bool view-rendered-auto-create = False
---------------------------------------

When True, the plugin will create a rendering pane automatically.

'''
</t>
<t tx="ekr.20241206101317.57"></t>
<t tx="ekr.20241206101317.58">@language md

# Title

This is **bold** and *italics*.

More info at [Leo's home page](&lt;http://leoeditor.com/).

### Section

- Bullet list
- Another item
- Third item.
</t>
<t tx="ekr.20241206101317.59">Inline:
    
\int_{b^a} \, x^2 \, dx: :math:`\int_{b^a} \, x^2 \, dx`

d/dx \int_{0^x}\,f(u)\,du=f(x): :math:`d/dx \int_{0^x}\,f(u)\,du=f(x)`

\int_{0^x}: :math:`\int_{0^x}`

block:

.. math::

    x + 5 = -3

    \int_{b^a} \, x^2 \, dx

    d / dx \, \left(\int_{0^x} f(u)\,du\right)=f(x)

    d / dx (\int_{0^x} f(u)\,du ) = f(x)

    y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})
    
Alignment with \\\\ in \\begin{equation}:

.. math::

    \begin{equation}
    x + 5 = -3 \\
    \int_{b^a} \, x^2 \, dx\\
    d / dx \left(\int_{0^x} f(u)\,du\right)=f(x)\\
    d / dx (\int_{0^x} f(u)\,du) = f(x)\\
    y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})
    \end{equation}
    
Inline: :math:`y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})`

Inline: :math:`(\sum_{t=0}^{N}\,f(t,k))/N`
    
The final output will be a vector :math:`Y` with :math:`K` elements, :math:`y_k`, where
:math:`y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})` is the probability that node :math:`k` is in class :math:`C_1` and
:math:`p(C_2|\mathbf{x}) = 1-p(C_1|\mathbf{x})`

</t>
<t tx="ekr.20241206101317.6">def setup(self, p=None):
    c = self.c
    self.end_on_exception = True # Good for debugging.
    self.delta = 10
    demo.set_text_delta(10)
    # self.geometry1 = self.get_top_geometry()
    # g.trace(self.get_top_geometry())
    p = g.findNodeAnywhere(c, 'Demo Area')
    self.root = p.copy() if p else None
    if p:
        p.expand()
        c.selectPosition(p)
    # c.frame.equalSizedPanes()
    c.redraw()
    self.set_youtube_position()
</t>
<t tx="ekr.20241206101317.60">@language md

- Item

[link](xxx)</t>
<t tx="ekr.20241206101317.61"></t>
<t tx="ekr.20241206101317.62">@language rest

#####
Title
#####

This is **bold** and *italics*.

More info at `Leo's home page &lt;http://leoeditor.com/&gt;`_.

Section
=======

- Bullet list
- Another item
- Third item.</t>
<t tx="ekr.20241206101317.63">@language rest
@wrap

- test2

*italic*

**bold**

@language python</t>
<t tx="ekr.20241206101317.64">@language python

def foo():
    pass</t>
<t tx="ekr.20241206101317.65">@language rest
@wrap
code::

    print('Hello world')
    for i in 0,1:
        print(i)
    
</t>
<t tx="ekr.20241206101317.66"></t>
<t tx="ekr.20241206101317.67">@language md
@wrap

[Leo's home page](https://leo-editor.github.io/leo-editor/)
</t>
<t tx="ekr.20241206101317.68">@language rest
@wrap

`Leo's home page &lt;https://leo-editor.github.io/leo-editor/&gt;`_

</t>
<t tx="ekr.20241206101317.69">@language rest
@wrap

`EKR mail &lt;http://mail.google.com/mail/#inbox&gt;`_</t>
<t tx="ekr.20241206101317.7">def setup_script(self):
    self.delete_widgets()
</t>
<t tx="ekr.20241206101317.70">@language md
@wrap

[weather.com](http://www.weather.com/weather/today/Madison+WI+53705?from=hp_promolocator&amp;lswe=53705&amp;lwsa=Weather36HourHealthCommand)
</t>
<t tx="ekr.20241206101317.71"></t>
<t tx="ekr.20241206101317.72" __bookmarks="7d7100580700000069735f6475706571014930300a732e">#
# a bar plot with errorbars
# import matplotlib # Make *sure* this is imported.
import matplotlib.pyplot as plt
import numpy as np
# import matplotlib.animation as animation
#
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N)  # the x locations for the groups
width = 0.35       # the width of the bars
fig, ax = plt.subplots()
c._matplot.connect_ax_to_widget(plt)
rects1 = ax.bar(ind, menMeans, width, color='r', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, womenMeans, width, color='y', yerr=womenStd)
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))

def autolabel(rects):
    # attach some text labels
    for rect in rects:
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
                '%d' % int(height),
                ha='center', va='bottom')

autolabel(rects1)
autolabel(rects2)
plt.draw()
</t>
<t tx="ekr.20241206101317.73" __bookmarks="7d7100580700000069735f6475706571014930300a732e">@language md

https://github.com/leo-editor/leo-editor/issues/1231

![bubbles](c:\leo.repo\leo-editor\leo\Icons\bubbles.svg)

![leo icon](C:\leo.repo\leo-editor\leo\Icons\SplashScreen.ico)</t>
<t tx="ekr.20241206101317.74">@language latex

% VR: latex rendering not ready for Python 3.
% VR3: doesn't work

\begin{align}
  \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} &amp; = \frac{4\pi}{c}\vec{\mathbf{j}} \\
  \nabla \cdot \vec{\mathbf{E}} &amp; = 4 \pi \rho \\
  \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} &amp; = \vec{\mathbf{0}} \\
  \nabla \cdot \vec{\mathbf{B}} &amp; = 0
\end{align}
</t>
<t tx="ekr.20241206101317.8">def teardown(self):
    c = self.c
    self.delete_all_widgets()
    # self.set_top_geometry(self.geometry1)
    # g.trace(self.get_top_geometry())
    if hasattr(self, 'delta') and self.delta &gt; 0:
        self.set_text_delta(-self.delta)
    if self.root and c.positionExists(self.root, trace=False):
        self.root.deleteAllChildren()
    p = c.lastTopLevel()
    p.expand()
    c.selectPosition(p)
    c.redraw()
</t>
<t tx="ekr.20241206101317.86"></t>
<t tx="ekr.20241206101317.87">Hello World!</t>
<t tx="ekr.20241206101317.88">c:\test\JinjaTemplates\TestTemplate.txt</t>
<t tx="ekr.20241206101317.89"></t>
<t tx="ekr.20241206101317.9">def teardown_script(self):
    if self.auto_run:
        # default wait.
        self.wait(0.5)</t>
<t tx="ekr.20241206101317.90">World</t>
<t tx="ekr.20241206101317.91">def update_jinja(self, s, keywords):
    pc = self
    h = self.c.p.h
    p = self.c.p
    c = self.c
    oldp = None

    #print "try act"
    if not h.startswith('@jinja'):
        #print("Not a @jinja node")
        return

    def find_root(p):
        for newp in p.parents():
            if newp.h.strip() == '@jinja':
                oldp, p = p, newp
                #print("Found @jinja node")
                return oldp, p

    def find_inputs(p):
        for newp in p.parents():
            if newp.h.strip() == '@jinja inputs':
                oldp, p = p, newp
                _, p = find_root(p) 
                return oldp, p
        
    # if on jinja node's children, find the parent
    if h.strip() == '@jinja template' or h.strip() == '@jinja inputs':
        # not at @jinja, find from parents
        oldp, p = find_root(p)

    elif h.startswith('@jinja variable'):
        # not at @jinja, first find @jinja inputs, then @jinja
        oldp, p = find_inputs(p) 

    def untangle(c,p):
    
        return g.getScript(c,p,
            useSelectedText=False,
            useSentinels=False)

    template_data = {}
    for child in p.children():
        if child.h == '@jinja template':
            template_path = g.os_path_finalize_join(c.getNodePath(p), untangle(c, child).strip())
            #print("template_path: ", template_path)
        elif child.h == '@jinja inputs':
            for template_var_node in child.children():
                template_data[template_var_node.h.replace('@jinja variable', '').strip()] = untangle(c, template_var_node).strip()
            #print("template_data: ", template_data)

    if not template_path:
        g.es("No template_path given. Your @jinja node should contain a child node 'template' with the path to the template (relative or absolute)")
        return

    #print "act"
    tmpl = Template(Path(template_path).read_text())
    out = tmpl.render(template_data)
    w = pc.ensure_text_widget()
    pc.show()
    w.setPlainText(out)
    p.b = out
    c.redraw(p)
    
    # focus back on entry node
    if oldp:
        c.redraw(oldp)</t>
<t tx="ekr.20241206101317.92">from jinja2 import Template
from markupsafe import escape
# template = Template('Hello {{ name }}!')
path = r'c:\test\JinjaTemplates\TestTemplate.txt'
s = open(path, 'r').read()
template = Template(s)
s = template.render(name='John Doe')
print(s)
print(escape('&lt;em&gt;Hello&lt;/em&gt;'))</t>
<t tx="ekr.20241206101345.1" annotate="7d71002858080000007072696f7269747971014d0f27580a000000707269736574646174657102580a000000323032342d30342d31307103752e"></t>
<t tx="ekr.20241206101345.100">class BaseCellExporter:
    """A class that represent a notebook cell as text"""
    @others
</t>
<t tx="ekr.20241206101345.101">default_comment_magics = None
parse_cell_language = True

def __init__(self, cell, default_language, fmt=None, unsupported_keys=None):
    self.fmt = fmt or {}
    self.ext = self.fmt.get("extension")
    self.cell_type = cell.cell_type
    self.source = cell_source(cell)
    self.unfiltered_metadata = cell.metadata
    self.metadata = filter_metadata(
        cell.metadata,
        self.fmt.get("cell_metadata_filter"),
        _IGNORE_CELL_METADATA,
        unsupported_keys=unsupported_keys,
    )
    if self.parse_cell_language:
        custom_cell_magics = self.fmt.get("custom_cell_magics", "").split(",")
        self.language, magic_args = cell_language(
            self.source, default_language, custom_cell_magics
        )

        if magic_args:
            self.metadata["magic_args"] = magic_args
    else:
        self.language = None

    if self.language and not self.ext.endswith(".Rmd"):
        self.metadata["language"] = self.language

    self.language = self.language or cell.metadata.get("language", default_language)
    self.default_language = default_language
    self.comment = _SCRIPT_EXTENSIONS.get(self.ext, {}).get("comment", "#")
    self.comment_suffix = _SCRIPT_EXTENSIONS.get(self.ext, {}).get(
        "comment_suffix", ""
    )
    self.comment_magics = self.fmt.get(
        "comment_magics", self.default_comment_magics
    )
    self.cell_metadata_json = self.fmt.get("cell_metadata_json", False)
    self.use_runtools = self.fmt.get("use_runtools", False)
    self.doxygen_equation_markers = self.fmt.get("doxygen_equation_markers", False)

    # how many blank lines before next cell
    self.lines_to_next_cell = cell.metadata.get("lines_to_next_cell")
    self.lines_to_end_of_cell_marker = cell.metadata.get(
        "lines_to_end_of_cell_marker"
    )

    if (
        cell.cell_type == "raw"
        and "active" not in self.metadata
        and not any(
            tag.startswith("active-") for tag in self.metadata.get("tags", [])
        )
    ):
        self.metadata["active"] = ""
</t>
<t tx="ekr.20241206101345.102">def is_code(self):
    """Is this cell a code cell?"""
    if self.cell_type == "code":
        return True
    if (
        self.cell_type == "raw"
        and "active" in self.metadata
        or any(tag.startswith("active-") for tag in self.metadata.get("tags", []))
    ):
        return True
    return False
</t>
<t tx="ekr.20241206101345.103">def use_triple_quotes(self):
    """Should this markdown cell use triple quote?"""
    if "cell_marker" not in self.unfiltered_metadata:
        return False
    cell_marker = self.unfiltered_metadata["cell_marker"]
    if cell_marker in ['"""', "'''"]:
        return True
    if "," not in cell_marker:
        return False
    left, right = cell_marker.split(",")
    return left[:3] == right[-3:] and left[:3] in ['"""', "'''"]
</t>
<t tx="ekr.20241206101345.104">def cell_to_text(self):
    """Return the text representation for the cell"""
    # Trigger cell marker in case we are using multiline quotes
    if self.cell_type != "code" and not self.metadata and self.use_triple_quotes():
        self.metadata["cell_type"] = self.cell_type

    # Go notebooks have '%%' or '%% -' magic commands that need to be escaped
    if self.default_language == "go" and self.language == "go":
        self.source = [
            re.sub(r"^(//\s*)*(%%\s*$|%%\s+-.*$)", r"\1//gonb:\2", line)
            for line in self.source
        ]

    if self.is_code():
        return self.code_to_text()

    source = copy(self.source)
    if not self.comment:
        escape_code_start(source, self.ext, None)
    return self.markdown_to_text(source)
</t>
<t tx="ekr.20241206101345.105">def markdown_to_text(self, source):
    """Escape the given source, for a markdown cell"""
    cell_markers = self.unfiltered_metadata.get(
        "cell_marker", self.fmt.get("cell_markers")
    )
    if cell_markers:
        if "," in cell_markers:
            left, right = cell_markers.split(",", 1)
        else:
            left = cell_markers + "\n"
            if cell_markers.startswith(("r", "R")):
                cell_markers = cell_markers[1:]
            right = "\n" + cell_markers

        if (
            left[:3] == right[-3:]
            or (left[:1] in ["r", "R"] and left[1:4] == right[-3:])
        ) and right[-3:] in ['"""', "'''"]:
            # Markdown cells that contain a backslash should be encoded as raw strings
            if (
                left[:1] not in ["r", "R"]
                and "\\" in "\n".join(source)
                and self.fmt.get("format_name") == "percent"
            ):
                left = "r" + left

            source = copy(source)
            source[0] = left + source[0]
            source[-1] = source[-1] + right
            return source

    if (
        self.comment
        and self.comment != "#'"
        and is_active(self.ext, self.metadata)
        and self.fmt.get("format_name") not in ["percent", "hydrogen"]
    ):
        source = copy(source)
        comment_magic(
            source,
            self.language,
            self.comment_magics,
            explicitly_code=self.cell_type == "code",
        )

    return comment_lines(source, self.comment, self.comment_suffix)
</t>
<t tx="ekr.20241206101345.106">def code_to_text(self):
    """Return the text representation of this cell as a code cell"""
    raise NotImplementedError("This method must be implemented in a sub-class")
</t>
<t tx="ekr.20241206101345.107">def remove_eoc_marker(self, text, next_text):
    """Remove end-of-cell marker when possible"""
    # pylint: disable=W0613,R0201
    return text
</t>
<t tx="ekr.20241206101345.108">class MarkdownCellExporter(BaseCellExporter):
    """A class that represent a notebook cell as Markdown"""
    @others
</t>
<t tx="ekr.20241206101345.109">default_comment_magics = False
cell_reader = MarkdownCellReader

def __init__(self, *args, **kwargs):
    BaseCellExporter.__init__(self, *args, **kwargs)
    self.comment = ""
</t>
<t tx="ekr.20241206101345.11">@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

A **bold** word.
</t>
<t tx="ekr.20241206101345.110">def html_comment(self, metadata, code="region"):
    """Protect a Markdown or Raw cell with HTML comments"""
    if metadata:
        region_start = [
            "&lt;!-- #" + code,
            metadata_to_text(metadata, plain_json=self.cell_metadata_json),
            "--&gt;",
        ]
        region_start = " ".join(region_start)
    else:
        region_start = f"&lt;!-- #{code} --&gt;"

    return [region_start] + self.source + [f"&lt;!-- #end{code} --&gt;"]
</t>
<t tx="ekr.20241206101345.111">def cell_to_text(self):
    """Return the text representation of a cell"""
    if self.cell_type == "markdown":
        if self.doxygen_equation_markers and self.cell_type == "markdown":
            self.source = markdown_to_doxygen("\n".join(self.source)).splitlines()

        # Is an explicit region required?
        if self.metadata:
            protect = True
        else:
            # Would the text be parsed to a shorter cell/a cell with a different type?
            cell, pos = self.cell_reader(self.fmt).read(self.source)
            protect = pos &lt; len(self.source) or cell.cell_type != self.cell_type
        if protect:
            return self.html_comment(
                self.metadata, self.metadata.pop("region_name", "region")
            )
        return self.source

    return self.code_to_text()
</t>
<t tx="ekr.20241206101345.112">def code_to_text(self):
    """Return the text representation of a code cell"""
    source = copy(self.source)
    comment_magic(source, self.language, self.comment_magics)

    if self.metadata.get("active") == "":
        self.metadata.pop("active")

    self.language = self.metadata.pop("language", self.language)
    if self.cell_type == "raw" and not is_active(self.ext, self.metadata, False):
        return self.html_comment(self.metadata, "raw")

    options = metadata_to_text(self.language, self.metadata)
    code_cell_delimiter = three_backticks_or_more(self.source)
    return [code_cell_delimiter + options] + source + [code_cell_delimiter]
</t>
<t tx="ekr.20241206101345.113">class RMarkdownCellExporter(MarkdownCellExporter):
    """A class that represent a notebook cell as R Markdown"""
    @others
</t>
<t tx="ekr.20241206101345.114">default_comment_magics = True
cell_reader = RMarkdownCellReader

def __init__(self, *args, **kwargs):
    MarkdownCellExporter.__init__(self, *args, **kwargs)
    self.ext = ".Rmd"
    self.comment = ""
</t>
<t tx="ekr.20241206101345.115">def code_to_text(self):
    """Return the text representation of a code cell"""
    active = is_active(self.ext, self.metadata)
    source = copy(self.source)

    if active:
        comment_magic(source, self.language, self.comment_magics)

    lines = []
    if not is_active(self.ext, self.metadata):
        self.metadata["eval"] = False
    options = metadata_to_rmd_options(
        self.language, self.metadata, self.use_runtools
    )
    lines.append(f"```{{{options}}}")
    lines.extend(source)
    lines.append("```")
    return lines
</t>
<t tx="ekr.20241206101345.116">def endofcell_marker(source, comment):
    """Issues #31 #38:  does the cell contain a blank line? In that case
    we add an end-of-cell marker"""
    endofcell = "-"
    while True:
        endofcell_re = re.compile(rf"^{re.escape(comment)}( )" + endofcell + r"\s*$")
        if list(filter(endofcell_re.match, source)):
            endofcell = endofcell + "-"
        else:
            return endofcell
</t>
<t tx="ekr.20241206101345.117">class LightScriptCellExporter(BaseCellExporter):
    """A class that represent a notebook cell as a Python or Julia script"""
    @others
</t>
<t tx="ekr.20241206101345.118">default_comment_magics = True
use_cell_markers = True
cell_marker_start = None
cell_marker_end = None

def __init__(self, *args, **kwargs):
    BaseCellExporter.__init__(self, *args, **kwargs)
    if "cell_markers" in self.fmt:
        if "," not in self.fmt["cell_markers"]:
            warnings.warn(
                "Ignored cell markers '{}' as it does not match the expected 'start,end' pattern".format(
                    self.fmt.pop("cell_markers")
                )
            )
        elif self.fmt["cell_markers"] != "+,-":
            self.cell_marker_start, self.cell_marker_end = self.fmt[
                "cell_markers"
            ].split(",", 1)
    for key in ["endofcell"]:
        if key in self.unfiltered_metadata:
            self.metadata[key] = self.unfiltered_metadata[key]
</t>
<t tx="ekr.20241206101345.119">def is_code(self):
    # Treat markdown cells with metadata as code cells (#66)
    if (self.cell_type == "markdown" and self.metadata) or self.use_triple_quotes():
        if is_active(self.ext, self.metadata):
            self.metadata["cell_type"] = self.cell_type
            self.source = self.markdown_to_text(self.source)
            self.cell_type = "code"
            self.unfiltered_metadata = copy(self.unfiltered_metadata)
            self.unfiltered_metadata.pop("cell_marker", "")
        return True
    return super().is_code()
</t>
<t tx="ekr.20241206101345.12">@language jupytext

# %%
def spam(s: str) -&gt; str:
    """A docstring a"""
    return '1234'

</t>
<t tx="ekr.20241206101345.120">def code_to_text(self):
    """Return the text representation of a code cell"""
    active = is_active(
        self.ext, self.metadata, same_language(self.language, self.default_language)
    )
    source = copy(self.source)
    escape_code_start(source, self.ext, self.language)
    comment_questions = self.metadata.pop("comment_questions", True)

    if active:
        comment_magic(source, self.language, self.comment_magics, comment_questions)
    else:
        source = self.markdown_to_text(source)

    if (
        active
        and comment_questions
        and need_explicit_marker(self.source, self.language, self.comment_magics)
    ) or self.explicit_start_marker(source):
        self.metadata["endofcell"] = self.cell_marker_end or endofcell_marker(
            source, self.comment
        )

    if not self.metadata or not self.use_cell_markers:
        return source

    lines = []
    endofcell = self.metadata["endofcell"]
    if endofcell == "-" or self.cell_marker_end:
        del self.metadata["endofcell"]

    cell_start = [self.comment, self.cell_marker_start or "+"]
    options = metadata_to_double_percent_options(
        self.metadata, self.cell_metadata_json
    )
    if options:
        cell_start.append(options)
    lines.append(" ".join(cell_start))
    lines.extend(source)
    lines.append(self.comment + f" {endofcell}")
    return lines
</t>
<t tx="ekr.20241206101345.121">def explicit_start_marker(self, source):
    """Does the python representation of this cell requires an explicit
    start of cell marker?"""
    if not self.use_cell_markers:
        return False
    if self.metadata:
        return True
    if self.cell_marker_start:
        start_code_re = re.compile(
            "^" + self.comment + r"\s*" + self.cell_marker_start + r"\s*(.*)$"
        )
        end_code_re = re.compile(
            "^" + self.comment + r"\s*" + self.cell_marker_end + r"\s*$"
        )
        if start_code_re.match(source[0]) or end_code_re.match(source[0]):
            return False

    if all([line.startswith(self.comment) for line in self.source]):
        return True
    if LightScriptCellReader(self.fmt).read(source)[1] &lt; len(source):
        return True

    return False
</t>
<t tx="ekr.20241206101345.122">def remove_eoc_marker(self, text, next_text):
    """Remove end of cell marker when next cell has an explicit start marker"""
    if self.cell_marker_start:
        return text

    if self.is_code() and text[-1] == self.comment + " -":
        # remove end of cell marker when redundant with next explicit marker
        if not next_text or next_text[0].startswith(self.comment + " +"):
            text = text[:-1]
            # When we do not need the end of cell marker, number of blank lines is the max
            # between that required at the end of the cell, and that required before the next cell.
            if self.lines_to_end_of_cell_marker and (
                self.lines_to_next_cell is None
                or self.lines_to_end_of_cell_marker &gt; self.lines_to_next_cell
            ):
                self.lines_to_next_cell = self.lines_to_end_of_cell_marker
        else:
            # Insert blank lines at the end of the cell
            blank_lines = self.lines_to_end_of_cell_marker
            if blank_lines is None:
                # two blank lines when required by pep8
                blank_lines = pep8_lines_between_cells(
                    text[:-1], next_text, self.ext
                )
                blank_lines = 0 if blank_lines &lt; 2 else 2
            text = text[:-1] + [""] * blank_lines + text[-1:]

    return text
</t>
<t tx="ekr.20241206101345.123">class BareScriptCellExporter(LightScriptCellExporter):
    """A class that writes notebook cells as scripts with no cell markers"""

    use_cell_markers = False
</t>
<t tx="ekr.20241206101345.124">class RScriptCellExporter(BaseCellExporter):
    """A class that can represent a notebook cell as a R script"""
    @others
</t>
<t tx="ekr.20241206101345.125">default_comment_magics = True

def __init__(self, *args, **kwargs):
    BaseCellExporter.__init__(self, *args, **kwargs)
    self.comment = "#'"
</t>
<t tx="ekr.20241206101345.126">def code_to_text(self):
    """Return the text representation of a code cell"""
    active = is_active(self.ext, self.metadata)
    source = copy(self.source)
    escape_code_start(source, self.ext, self.language)

    if active:
        comment_magic(source, self.language, self.comment_magics)

    if not active:
        source = ["# " + line if line else "#" for line in source]

    lines = []
    if not is_active(self.ext, self.metadata):
        self.metadata["eval"] = False
    options = metadata_to_rmd_options(None, self.metadata, self.use_runtools)
    if options:
        lines.append(f"#+ {options}")
    lines.extend(source)
    return lines
</t>
<t tx="ekr.20241206101345.127">class DoublePercentCellExporter(BaseCellExporter):  # pylint: disable=W0223
    """A class that can represent a notebook cell as a Spyder/VScode script (#59)"""
    @others
</t>
<t tx="ekr.20241206101345.128">default_comment_magics = True
parse_cell_language = True

def __init__(self, *args, **kwargs):
    BaseCellExporter.__init__(self, *args, **kwargs)
    self.cell_markers = self.fmt.get("cell_markers")
</t>
<t tx="ekr.20241206101345.129">def cell_to_text(self):
    """Return the text representation for the cell"""
    # Go notebooks have '%%' or '%% -' magic commands that need to be escaped
    if self.default_language == "go" and self.language == "go":
        self.source = [
            re.sub(r"^(//\s*)*(%%\s*$|%%\s+-.*$)", r"\1//gonb:\2", line)
            for line in self.source
        ]

    active = is_active(
        self.ext, self.metadata, same_language(self.language, self.default_language)
    )
    if (
        self.cell_type == "raw"
        and "active" in self.metadata
        and self.metadata["active"] == ""
    ):
        del self.metadata["active"]

    if not self.is_code():
        self.metadata["cell_type"] = self.cell_type

    options = metadata_to_double_percent_options(
        self.metadata, self.cell_metadata_json
    )
    indent = ""
    if self.is_code() and active and self.source:
        first_line = self.source[0]
        if first_line.strip():
            left_space = re.compile(r"^(\s*)").match(first_line)
            if left_space:
                indent = left_space.groups()[0]

    if options.startswith("%") or not options:
        lines = comment_lines(
            ["%%" + options], indent + self.comment, self.comment_suffix
        )
    else:
        lines = comment_lines(
            ["%% " + options], indent + self.comment, self.comment_suffix
        )

    if self.is_code() and active:
        source = copy(self.source)
        comment_magic(source, self.language, self.comment_magics)
        if source == [""]:
            return lines
        return lines + source

    return lines + self.markdown_to_text(self.source)
</t>
<t tx="ekr.20241206101345.13">@language jupytext

@others  # test.


@language python

@others  # test2.</t>
<t tx="ekr.20241206101345.130">class HydrogenCellExporter(DoublePercentCellExporter):  # pylint: disable=W0223
    """A class that can represent a notebook cell as a Hydrogen script (#59)"""

    default_comment_magics = False
    parse_cell_language = False
</t>
<t tx="ekr.20241206101345.131">class SphinxGalleryCellExporter(BaseCellExporter):  # pylint: disable=W0223
    """A class that can represent a notebook cell as a
    Sphinx Gallery script (#80)"""
    @others
</t>
<t tx="ekr.20241206101345.132">default_cell_marker = "#" * 79
default_comment_magics = True

def __init__(self, *args, **kwargs):
    BaseCellExporter.__init__(self, *args, **kwargs)
    self.comment = "#"

    for key in ["cell_marker"]:
        if key in self.unfiltered_metadata:
            self.metadata[key] = self.unfiltered_metadata[key]

    if self.fmt.get("rst2md"):
        raise ValueError(
            "The 'rst2md' option is a read only option. The reverse conversion is not "
            "implemented. Please either deactivate the option, or save to another format."
        )  # pragma: no cover
</t>
<t tx="ekr.20241206101345.133">def cell_to_text(self):
    """Return the text representation for the cell"""
    if self.cell_type == "code":
        source = copy(self.source)
        return comment_magic(source, self.language, self.comment_magics)

    if "cell_marker" in self.metadata:
        cell_marker = self.metadata.pop("cell_marker")
    else:
        cell_marker = self.default_cell_marker

    if self.source == [""]:
        return [cell_marker] if cell_marker in ['""', "''"] else ['""']

    if cell_marker in ['"""', "'''"]:
        return [cell_marker] + self.source + [cell_marker]

    return [
        cell_marker
        if cell_marker.startswith("#" * 20)
        else self.default_cell_marker
    ] + comment_lines(self.source, self.comment, self.comment_suffix)
</t>
<t tx="ekr.20241206101345.15">@language md
@wrap
@nosearch

https://github.com/leo-editor/leo-editor/issues/4146
PR: https://github.com/leo-editor/leo-editor/pull/4147

# Section
## Section

@language python

def spam():
    if </t>
<t tx="ekr.20241206101345.156">"""Combine source and outputs from two notebooks
"""
import re
from copy import copy

from nbformat import NotebookNode

from .cell_metadata import _IGNORE_CELL_METADATA
from .formats import long_form_one_format
from .header import _DEFAULT_NOTEBOOK_METADATA
from .metadata_filter import restore_filtered_metadata

_BLANK_LINE = re.compile(r"^\s*$")


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.157">def black_invariant(text, chars=None):
    """Remove characters that may be changed when reformatting the text with black"""
    if chars is None:
        chars = [" ", "\t", "\n", ",", "'", '"', "(", ")", "\\"]

    for char in chars:
        text = text.replace(char, "")
    return text
</t>
<t tx="ekr.20241206101345.158">def same_content(ref, test, endswith=False):
    """Is the content of two cells the same, up to reformatting by black"""
    ref = black_invariant(ref)
    test = black_invariant(test)

    if endswith and test:
        return ref.endswith(test)
    return ref == test
</t>
<t tx="ekr.20241206101345.159">def combine_inputs_with_outputs(nb_source, nb_outputs, fmt=None):
    """Return a notebook that combines the text and metadata from the first notebook,
    with the outputs and metadata of the second notebook."""
    # nbformat version number taken from the notebook with outputs
    assert (
        nb_outputs.nbformat == nb_source.nbformat
    ), "The notebook with outputs is in format {}.{}, please upgrade it to {}.x".format(
        nb_outputs.nbformat, nb_outputs.nbformat_minor, nb_source.nbformat
    )
    nb_source.nbformat_minor = nb_outputs.nbformat_minor

    fmt = long_form_one_format(fmt)
    text_repr = nb_source.metadata.get("jupytext", {}).get("text_representation", {})
    ext = fmt.get("extension") or text_repr.get("extension")
    format_name = fmt.get("format_name") or text_repr.get("format_name")

    notebook_metadata_filter = nb_source.metadata.get("jupytext", {}).get(
        "notebook_metadata_filter"
    )
    if notebook_metadata_filter == "-all":
        nb_metadata = nb_outputs.metadata

    else:
        nb_metadata = restore_filtered_metadata(
            nb_source.metadata,
            nb_outputs.metadata,
            notebook_metadata_filter,
            _DEFAULT_NOTEBOOK_METADATA,
        )

    source_is_md_version_one = (
        ext in [".md", ".markdown", ".Rmd"] and text_repr.get("format_version") == "1.0"
    )
    if nb_metadata.get("jupytext", {}).get("formats") or ext in [
        ".md",
        ".markdown",
        ".Rmd",
    ]:
        nb_metadata.get("jupytext", {}).pop("text_representation", None)

    if not nb_metadata.get("jupytext", {}):
        nb_metadata.pop("jupytext", {})

    if format_name in ["nomarker", "sphinx"] or source_is_md_version_one:
        cell_metadata_filter = "-all"
    else:
        cell_metadata_filter = nb_metadata.get("jupytext", {}).get(
            "cell_metadata_filter"
        )

    outputs_map = map_outputs_to_inputs(nb_source.cells, nb_outputs.cells)

    cells = []
    for source_cell, j in zip(nb_source.cells, outputs_map):
        if j is None:
            cells.append(source_cell)
            continue

        output_cell = nb_outputs.cells[j]

        # Outputs and optional attributes are taken from the notebook with outputs
        cell = copy(output_cell)

        # Cell text is taken from the source notebook
        cell.source = source_cell.source

        # We also restore the cell metadata that has been filtered
        cell.metadata = restore_filtered_metadata(
            source_cell.metadata,
            output_cell.metadata,
            # The 'spin' format does not allow metadata on non-code cells
            "-all"
            if format_name == "spin" and source_cell.cell_type != "code"
            else cell_metadata_filter,
            _IGNORE_CELL_METADATA,
        )

        cells.append(cell)

    # We call NotebookNode rather than new_notebook as we don't want to validate
    # the notebook (some of the notebook in the collection of test notebooks
    # do have some invalid properties - probably inherited from an older version
    # of the notebook format).
    return NotebookNode(
        cells=cells,
        metadata=nb_metadata,
        nbformat=nb_outputs.nbformat,
        nbformat_minor=nb_outputs.nbformat_minor,
    )
</t>
<t tx="ekr.20241206101345.16">@killcolor

@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

A **bold** word.

# %%
import pandas as pd  # A comment

def spam(s: str) -&gt; str:
    return 'hi123'
    
# %% [markdown]

*Italics* and **bold**.
</t>
<t tx="ekr.20241206101345.160">def map_outputs_to_inputs(cells_inputs, cells_outputs):
    """Returns a map i-&gt;(j or None) that maps the cells with outputs to the input cells"""
    n_in = len(cells_inputs)
    n_out = len(cells_outputs)
    outputs_map = [None] * n_in

    # First rule: match based on cell type, content, in increasing order, for each cell type
    first_unmatched_output_per_cell_type = {}
    for i in range(n_in):
        cell_input = cells_inputs[i]
        for j in range(
            first_unmatched_output_per_cell_type.get(cell_input.cell_type, 0), n_out
        ):
            cell_output = cells_outputs[j]
            if cell_input.cell_type == cell_output.cell_type and same_content(
                cell_input.source, cell_output.source
            ):
                outputs_map[i] = j
                first_unmatched_output_per_cell_type[cell_input.cell_type] = j + 1
                break

    # Second rule: match unused outputs based on cell type and content
    # Third rule: is the new cell the final part of a previous cell with outputs?
    unused_ouputs = set(range(n_out)).difference(outputs_map)
    for endswith in [False, True]:
        if not unused_ouputs:
            return outputs_map

        for i in range(n_in):
            if outputs_map[i] is not None:
                continue
            cell_input = cells_inputs[i]
            for j in unused_ouputs:
                cell_output = cells_outputs[j]
                if cell_input.cell_type == cell_output.cell_type and same_content(
                    cell_output.source, cell_input.source, endswith
                ):
                    outputs_map[i] = j
                    unused_ouputs.remove(j)
                    break

    # Fourth rule: match based on increasing index (and cell type) for non-empty cells
    if not unused_ouputs:
        return outputs_map

    prev_j = -1
    for i in range(n_in):
        if outputs_map[i] is not None:
            prev_j = outputs_map[i]
            continue

        j = prev_j + 1
        if j not in unused_ouputs:
            continue

        cell_input = cells_inputs[i]
        cell_output = cells_outputs[j]
        if (
            cell_input.cell_type == cell_output.cell_type
            and cell_input.source.strip() != ""
        ):
            outputs_map[i] = j
            unused_ouputs.remove(j)
            prev_j = j

    return outputs_map
</t>
<t tx="ekr.20241206101345.161">"""Compare two Jupyter notebooks"""

import difflib
import json
import os
import re

from jupytext.paired_paths import full_path

from .cell_metadata import _IGNORE_CELL_METADATA
from .combine import combine_inputs_with_outputs
from .formats import check_auto_ext, long_form_one_format
from .header import _DEFAULT_NOTEBOOK_METADATA
from .jupytext import read, reads, write, writes
from .metadata_filter import filter_metadata

_BLANK_LINE = re.compile(r"^\s*$")


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.162">def _multilines(obj):
    try:
        lines = obj.splitlines()
        return lines + [""] if obj.endswith("\n") else lines
    except AttributeError:
        # Remove the final blank space on Python 2.7
        # return json.dumps(obj, indent=True, sort_keys=True).splitlines()
        return [
            line.rstrip()
            for line in json.dumps(obj, indent=True, sort_keys=True).splitlines()
        ]
</t>
<t tx="ekr.20241206101345.163">def compare(
    actual, expected, actual_name="actual", expected_name="expected", return_diff=False
):
    """Compare two strings, lists or dict-like objects"""
    if actual != expected:
        diff = difflib.unified_diff(
            _multilines(expected),
            _multilines(actual),
            expected_name,
            actual_name,
            lineterm="",
        )
        if expected_name == "" and actual_name == "":
            diff = list(diff)[2:]
        diff = "\n".join(diff)
        if return_diff:
            return diff
        raise AssertionError("\n" + diff)
    return "" if return_diff else None
</t>
<t tx="ekr.20241206101345.164">def filtered_cell(cell, preserve_outputs, cell_metadata_filter):
    """Cell type, metadata and source from given cell"""
    filtered = {
        "cell_type": cell.cell_type,
        "source": cell.source,
        "metadata": filter_metadata(
            cell.metadata, cell_metadata_filter, _IGNORE_CELL_METADATA
        ),
    }

    if preserve_outputs:
        for key in ["execution_count", "outputs"]:
            if key in cell:
                filtered[key] = cell[key]

    return filtered
</t>
<t tx="ekr.20241206101345.165">def filtered_notebook_metadata(notebook, ignore_display_name=False):
    """Notebook metadata, filtered for metadata added by Jupytext itself"""
    metadata = filter_metadata(
        notebook.metadata,
        notebook.metadata.get("jupytext", {}).get("notebook_metadata_filter"),
        _DEFAULT_NOTEBOOK_METADATA,
    )

    # The display name for the kernel might change (Quarto format on the CI)
    if ignore_display_name:
        metadata.get("kernelspec", {}).pop("display_name", None)

    if "jupytext" in metadata:
        del metadata["jupytext"]
    return metadata
</t>
<t tx="ekr.20241206101345.166">class NotebookDifference(Exception):
    """Report notebook differences"""
</t>
<t tx="ekr.20241206101345.167">def same_content(ref_source, test_source, allow_removed_final_blank_line):
    """Is the content of two cells the same, except for an optional final blank line?"""
    if ref_source == test_source:
        return True

    if not allow_removed_final_blank_line:
        return False

    # Is ref identical to test, plus one blank line?
    ref_source = ref_source.splitlines()
    test_source = test_source.splitlines()

    if not ref_source:
        return False

    if ref_source[:-1] != test_source:
        return False

    return _BLANK_LINE.match(ref_source[-1])
</t>
<t tx="ekr.20241206101345.168">def compare_notebooks(
    notebook_actual,
    notebook_expected,
    fmt=None,
    allow_expected_differences=True,
    raise_on_first_difference=True,
    compare_outputs=False,
    compare_ids=None,
):
    """Compare the two notebooks, and raise with a meaningful message
    that explains the differences, if any"""
    fmt = long_form_one_format(fmt)
    format_name = fmt.get("format_name")

    if (
        format_name == "sphinx"
        and notebook_actual.cells
        and notebook_actual.cells[0].source == "%matplotlib inline"
    ):
        notebook_actual.cells = notebook_actual.cells[1:]

    if compare_ids is None:
        compare_ids = compare_outputs

    modified_cells, modified_cell_metadata = compare_cells(
        notebook_actual.cells,
        notebook_expected.cells,
        raise_on_first_difference,
        compare_outputs=compare_outputs,
        compare_ids=compare_ids,
        cell_metadata_filter=notebook_actual.get("jupytext", {}).get(
            "cell_metadata_filter"
        ),
        allow_missing_code_cell_metadata=(
            allow_expected_differences and format_name == "sphinx"
        ),
        allow_missing_markdown_cell_metadata=(
            allow_expected_differences and format_name in ["sphinx", "spin"]
        ),
        allow_filtered_cell_metadata=allow_expected_differences,
        allow_removed_final_blank_line=allow_expected_differences,
    )

    # Compare notebook metadata
    modified_metadata = False
    try:
        ignore_display_name = (
            fmt.get("extension") == ".qmd" and allow_expected_differences
        )
        compare(
            filtered_notebook_metadata(notebook_actual, ignore_display_name),
            filtered_notebook_metadata(notebook_expected, ignore_display_name),
        )
    except AssertionError as error:
        if raise_on_first_difference:
            raise NotebookDifference(f"Notebook metadata differ: {str(error)}")
        modified_metadata = True

    error = []
    if modified_cells:
        error.append(
            "Cells {} differ ({}/{})".format(
                ",".join([str(i) for i in modified_cells]),
                len(modified_cells),
                len(notebook_expected.cells),
            )
        )
    if modified_cell_metadata:
        error.append(
            "Cell metadata '{}' differ".format(
                "', '".join([str(i) for i in modified_cell_metadata])
            )
        )
    if modified_metadata:
        error.append("Notebook metadata differ")

    if error:
        raise NotebookDifference(" | ".join(error))
</t>
<t tx="ekr.20241206101345.169">def compare_cells(
    actual_cells,
    expected_cells,
    raise_on_first_difference=True,
    compare_outputs=True,
    compare_ids=True,
    cell_metadata_filter=None,
    allow_missing_code_cell_metadata=False,
    allow_missing_markdown_cell_metadata=False,
    allow_filtered_cell_metadata=False,
    allow_removed_final_blank_line=False,
):
    """Compare two collection of notebook cells"""
    test_cell_iter = iter(actual_cells)
    modified_cells = set()
    modified_cell_metadata = set()

    for i, ref_cell in enumerate(expected_cells, 1):
        try:
            test_cell = next(test_cell_iter)
        except StopIteration:
            if raise_on_first_difference:
                raise NotebookDifference(
                    "No cell corresponding to {} cell #{}:\n{}".format(
                        ref_cell.cell_type, i, ref_cell.source
                    )
                )
            modified_cells.update(range(i, len(expected_cells) + 1))
            break

        ref_lines = [
            line for line in ref_cell.source.splitlines() if not _BLANK_LINE.match(line)
        ]
        test_lines = []

        # 1. test cell type
        if ref_cell.cell_type != test_cell.cell_type:
            if raise_on_first_difference:
                raise NotebookDifference(
                    "Unexpected cell type '{}' for {} cell #{}:\n{}".format(
                        test_cell.cell_type, ref_cell.cell_type, i, ref_cell.source
                    )
                )
            modified_cells.add(i)

        # Compare cell ids (introduced in nbformat 5.1.0)
        if compare_ids and test_cell.get("id") != ref_cell.get("id"):
            if raise_on_first_difference:
                raise NotebookDifference(
                    f"Cell ids differ on {test_cell['cell_type']} cell #{i}: "
                    f"'{test_cell.get('id')}' != '{ref_cell.get('id')}'"
                )
            modified_cells.add(i)

        # 2. test cell metadata
        if (ref_cell.cell_type == "code" and not allow_missing_code_cell_metadata) or (
            ref_cell.cell_type != "code" and not allow_missing_markdown_cell_metadata
        ):
            ref_metadata = ref_cell.metadata
            test_metadata = test_cell.metadata
            if allow_filtered_cell_metadata:
                ref_metadata = {
                    key: ref_metadata[key]
                    for key in ref_metadata
                    if key not in _IGNORE_CELL_METADATA
                }
                test_metadata = {
                    key: test_metadata[key]
                    for key in test_metadata
                    if key not in _IGNORE_CELL_METADATA
                }

            if ref_metadata != test_metadata:
                if raise_on_first_difference:
                    try:
                        compare(test_metadata, ref_metadata)
                    except AssertionError as error:
                        raise NotebookDifference(
                            "Metadata differ on {} cell #{}: {}\nCell content:\n{}".format(
                                test_cell.cell_type, i, str(error), ref_cell.source
                            )
                        )
                else:
                    modified_cell_metadata.update(
                        set(test_metadata).difference(ref_metadata)
                    )
                    modified_cell_metadata.update(
                        set(ref_metadata).difference(test_metadata)
                    )
                    for key in set(ref_metadata).intersection(test_metadata):
                        if ref_metadata[key] != test_metadata[key]:
                            modified_cell_metadata.add(key)

        test_lines.extend(
            [
                line
                for line in test_cell.source.splitlines()
                if not _BLANK_LINE.match(line)
            ]
        )

        # 3. test cell content
        if ref_lines != test_lines:
            if raise_on_first_difference:
                try:
                    compare("\n".join(test_lines), "\n".join(ref_lines))
                except AssertionError as error:
                    raise NotebookDifference(
                        "Cell content differ on {} cell #{}: {}".format(
                            test_cell.cell_type, i, str(error)
                        )
                    )
            else:
                modified_cells.add(i)

        # 3. bis test entire cell content
        if not same_content(
            ref_cell.source, test_cell.source, allow_removed_final_blank_line
        ):
            if ref_cell.source != test_cell.source:
                if raise_on_first_difference:
                    diff = compare(test_cell.source, ref_cell.source, return_diff=True)
                    raise NotebookDifference(
                        "Cell content differ on {} cell #{}: {}".format(
                            test_cell.cell_type, i, diff
                        )
                    )
                modified_cells.add(i)

        if not compare_outputs:
            continue

        if ref_cell.cell_type != "code":
            continue

        ref_cell = filtered_cell(
            ref_cell,
            preserve_outputs=compare_outputs,
            cell_metadata_filter=cell_metadata_filter,
        )
        test_cell = filtered_cell(
            test_cell,
            preserve_outputs=compare_outputs,
            cell_metadata_filter=cell_metadata_filter,
        )

        try:
            compare(test_cell, ref_cell)
        except AssertionError as error:
            if raise_on_first_difference:
                raise NotebookDifference(
                    "Cell outputs differ on {} cell #{}: {}".format(
                        test_cell["cell_type"], i, str(error)
                    )
                )
            modified_cells.add(i)

    # More cells in the actual notebook?
    remaining_cell_count = 0
    while True:
        try:
            test_cell = next(test_cell_iter)
            if raise_on_first_difference:
                raise NotebookDifference(
                    "Additional {} cell: {}".format(
                        test_cell.cell_type, test_cell.source
                    )
                )
            remaining_cell_count += 1
        except StopIteration:
            break

    if remaining_cell_count and not raise_on_first_difference:
        modified_cells.update(
            range(
                len(expected_cells) + 1,
                len(expected_cells) + 1 + remaining_cell_count,
            )
        )

    return modified_cells, modified_cell_metadata
</t>
<t tx="ekr.20241206101345.17"># @killcolor

@nocolor
@language python

def spam():
    pass

@color

def spam2():
    pass</t>
<t tx="ekr.20241206101345.170">def test_round_trip_conversion(
    notebook, fmt, update, allow_expected_differences=True, stop_on_first_error=True
):
    """Test round trip conversion for a Jupyter notebook"""
    text = writes(notebook, fmt)
    round_trip = reads(text, fmt)

    if update:
        round_trip = combine_inputs_with_outputs(round_trip, notebook, fmt)

    compare_notebooks(
        round_trip,
        notebook,
        fmt,
        allow_expected_differences,
        raise_on_first_difference=stop_on_first_error,
    )
</t>
<t tx="ekr.20241206101345.171"># The functions below are used in the Jupytext text collection
def create_mirror_file_if_missing(mirror_file, notebook, fmt):
    if not os.path.isfile(mirror_file):
        write(notebook, mirror_file, fmt=fmt)
</t>
<t tx="ekr.20241206101345.172">def assert_conversion_same_as_mirror(nb_file, fmt, mirror_name, compare_notebook=False):
    """This function is used in the tests"""
    dirname, basename = os.path.split(nb_file)
    file_name, org_ext = os.path.splitext(basename)
    fmt = long_form_one_format(fmt)
    notebook = read(nb_file, fmt=fmt)
    fmt = check_auto_ext(fmt, notebook.metadata, "")
    ext = fmt["extension"]
    mirror_file = os.path.join(
        dirname, "..", "..", "outputs", mirror_name, full_path(file_name, fmt)
    )

    # it's better not to have Jupytext metadata in test notebooks:
    if fmt == "ipynb" and "jupytext" in notebook.metadata:  # pragma: no cover
        notebook.metadata.pop("jupytext")
        write(nb_file, fmt=fmt)

    create_mirror_file_if_missing(mirror_file, notebook, fmt)

    # Compare the text representation of the two notebooks
    if compare_notebook:
        # Read and convert the mirror file to the latest nbformat version if necessary
        nb_mirror = read(mirror_file, as_version=notebook.nbformat)
        nb_mirror.nbformat_minor = notebook.nbformat_minor
        compare_notebooks(nb_mirror, notebook)
        return
    elif ext == ".ipynb":
        notebook = read(mirror_file)
        fmt.update({"extension": org_ext})
        actual = writes(notebook, fmt)
        with open(nb_file, encoding="utf-8") as fp:
            expected = fp.read()
    else:
        actual = writes(notebook, fmt)
        with open(mirror_file, encoding="utf-8") as fp:
            expected = fp.read()

    if not actual.endswith("\n"):
        actual = actual + "\n"
    compare(actual, expected)

    # Compare the two notebooks
    if ext != ".ipynb":
        notebook = read(nb_file)
        nb_mirror = read(mirror_file, fmt=fmt)

        if fmt.get("format_name") == "sphinx":
            nb_mirror.cells = nb_mirror.cells[1:]
            for cell in notebook.cells:
                cell.metadata = {}
            for cell in nb_mirror.cells:
                cell.metadata = {}

        compare_notebooks(nb_mirror, notebook, fmt)

        nb_mirror = combine_inputs_with_outputs(nb_mirror, notebook)
        compare_notebooks(nb_mirror, notebook, fmt, compare_outputs=True)
</t>
<t tx="ekr.20241206101345.173">def notebook_model(nb):
    """Return a notebook model, with content a
    dictionary rather than a notebook object.
    To be used in tests only."""
    return dict(type="notebook", content=json.loads(json.dumps(nb)))
</t>
<t tx="ekr.20241206101345.174">"""Find and read Jupytext configuration files"""
import json
import os

try:
    import tomllib
except ImportError:
    import tomli as tomllib

import warnings

import yaml
from traitlets import Bool, Dict, Enum, Float, List, Unicode, Union
from traitlets.config import Configurable
from traitlets.config.loader import PyFileConfigLoader
from traitlets.traitlets import TraitError

from .formats import (
    NOTEBOOK_EXTENSIONS,
    long_form_multiple_formats,
    long_form_one_format,
    short_form_multiple_formats,
)


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.175">class JupytextConfigurationError(ValueError):
    """Error in the specification of the format for the text notebook"""
</t>
<t tx="ekr.20241206101345.176">JUPYTEXT_CONFIG_FILES = [
    "jupytext",
    "jupytext.toml",
    "jupytext.yml",
    "jupytext.yaml",
    "jupytext.json",
]

JUPYTEXT_CONFIG_FILES.extend(
    ["." + filename for filename in JUPYTEXT_CONFIG_FILES] + [".jupytext.py"]
)

PYPROJECT_FILE = "pyproject.toml"

JUPYTEXT_CEILING_DIRECTORIES = [
    path
    for path in os.environ.get("JUPYTEXT_CEILING_DIRECTORIES", "").split(":")
    if path
]


class JupytextConfiguration(Configurable):
    """Jupytext Configuration's options"""
    @others
</t>
<t tx="ekr.20241206101345.177">formats = Union(
    [Unicode(), List(Unicode()), Dict(Unicode())],
    help="Save notebooks to these file extensions. "
    "Can be any of ipynb,Rmd,md,jl,py,R,nb.jl,nb.py,nb.R "
    "comma separated. If you want another format than the "
    "default one, append the format name to the extension, "
    "e.g. ipynb,py:percent to save the notebook to "
    "hydrogen/spyder/vscode compatible scripts",
    config=True,
)
default_jupytext_formats = Unicode(
    help="Deprecated. Use 'formats' instead", config=True
)

preferred_jupytext_formats_save = Unicode(
    help="Preferred format when saving notebooks as text, per extension. "
    'Use "jl:percent,py:percent,R:percent" if you want to save '
    "Julia, Python and R scripts in the double percent format and "
    'only write "jupytext_formats": "py" in the notebook metadata.',
    config=True,
)

preferred_jupytext_formats_read = Unicode(
    help="Preferred format when reading notebooks from text, per "
    'extension. Use "py:sphinx" if you want to read all python '
    "scripts as Sphinx gallery scripts.",
    config=True,
)

notebook_metadata_filter = Unicode(
    help="Notebook metadata that should be save in the text representations. "
    "Examples: 'all', '-all', 'widgets,nteract', 'kernelspec,jupytext-all'",
    config=True,
)

default_notebook_metadata_filter = Unicode(
    "", help="Deprecated. Use 'notebook_metadata_filter' instead", config=True
)

hide_notebook_metadata = Enum(
    values=[True, False],
    allow_none=True,
    help="Should the notebook metadata be wrapped into an HTML comment in the Markdown format?",
    config=True,
)

root_level_metadata_as_raw_cell = Bool(
    True,
    help="Should the root level metadata of text documents (like the fields 'title' or 'author' in "
    "R Markdown document) appear as a raw cell in the notebook (True), or go to the notebook"
    "metadata?",
    config=True,
)

cell_metadata_filter = Unicode(
    help="Cell metadata that should be saved in the text representations. "
    "Examples: 'all', 'hide_input,hide_output'",
    config=True,
)

default_cell_metadata_filter = Unicode(
    "", help="Deprecated. Use 'cell_metadata_filter' instead", config=True
)

comment_magics = Enum(
    values=[True, False],
    allow_none=True,
    help="Should Jupyter magic commands be commented out in the text representation?",
    config=True,
)

split_at_heading = Bool(
    False,
    help="Split markdown cells on headings (Markdown and R Markdown formats only)",
    config=True,
)

sphinx_convert_rst2md = Bool(
    False,
    help="When opening a Sphinx Gallery script, convert the reStructuredText to markdown",
    config=True,
)

doxygen_equation_markers = Bool(
    False,
    help="Should equation markers use the DOxygen format? "
    "(see https://github.com/mwouts/jupytext/issues/517)",
    config=True,
)

outdated_text_notebook_margin = Float(
    1.0,
    help="Refuse to overwrite inputs of a ipynb notebooks with those of a "
    "text notebook when the text notebook plus margin is older than "
    "the ipynb notebook (NB: This option is ignored by Jupytext CLI)",
    config=True,
)

cm_config_log_level = Enum(
    values=["warning", "info", "info_if_changed", "debug", "none"],
    default_value="info_if_changed",
    help="The log level for config file logs in the Jupytext contents manager",
    config=True,
)

cell_markers = Unicode(
    help='Start and end cell markers for the light format, comma separated. Use "{{{,}}}" to mark cells'
    'as foldable regions in Vim, and "region,endregion" to mark cells as Vscode/PyCharm regions',
    config=True,
)

default_cell_markers = Unicode(
    help="Deprecated. Use 'cell_markers' instead", config=True
)

notebook_extensions = Union(
    [List(Unicode(), NOTEBOOK_EXTENSIONS), Unicode()],
    help="A list of notebook extensions",
    config=True,
)

custom_cell_magics = Unicode(
    help='A comma separated list of cell magics. Use e.g. custom_cell_magics = "configure,local" '
    'if you want code cells starting with the Spark magic cell commands "configure" and "local" '
    "to be commented out when converted to scripts.",
    config=True,
)

def set_default_format_options(self, format_options, read=False):
    """Set default format option"""
    if self.default_notebook_metadata_filter:
        warnings.warn(
            "The option 'default_notebook_metadata_filter' is deprecated. "
            "Please use 'notebook_metadata_filter' instead.",
            FutureWarning,
        )
        format_options.setdefault(
            "notebook_metadata_filter", self.default_notebook_metadata_filter
        )
    if self.notebook_metadata_filter:
        format_options.setdefault(
            "notebook_metadata_filter", self.notebook_metadata_filter
        )
    if self.default_cell_metadata_filter:
        warnings.warn(
            "The option 'default_cell_metadata_filter' is deprecated. "
            "Please use 'cell_metadata_filter' instead.",
            FutureWarning,
        )
        format_options.setdefault(
            "cell_metadata_filter", self.default_cell_metadata_filter
        )
    if self.cell_metadata_filter:
        format_options.setdefault("cell_metadata_filter", self.cell_metadata_filter)
    if self.hide_notebook_metadata is not None:
        format_options.setdefault(
            "hide_notebook_metadata", self.hide_notebook_metadata
        )
    if self.root_level_metadata_as_raw_cell is False:
        format_options.setdefault(
            "root_level_metadata_as_raw_cell", self.root_level_metadata_as_raw_cell
        )
    if self.comment_magics is not None:
        format_options.setdefault("comment_magics", self.comment_magics)
    if self.split_at_heading:
        format_options.setdefault("split_at_heading", self.split_at_heading)
    if self.doxygen_equation_markers:
        format_options.setdefault(
            "doxygen_equation_markers", self.doxygen_equation_markers
        )
    if not read:
        if self.default_cell_markers:
            warnings.warn(
                "The option 'default_cell_markers' is deprecated. "
                "Please use 'cell_markers' instead.",
                FutureWarning,
            )
            format_options.setdefault("cell_markers", self.default_cell_markers)
        if self.cell_markers:
            format_options.setdefault("cell_markers", self.cell_markers)
    if read and self.sphinx_convert_rst2md:
        format_options.setdefault("rst2md", self.sphinx_convert_rst2md)
    if self.custom_cell_magics:
        format_options.setdefault("custom_cell_magics", self.custom_cell_magics)
</t>
<t tx="ekr.20241206101345.178">def default_formats(self, path):
    """Return the default formats, if they apply to the current path #157"""
    from .paired_paths import InconsistentPath, base_path

    if self.default_jupytext_formats:
        warnings.warn(
            "The option 'default_jupytext_formats' is deprecated. "
            "Please use 'formats' instead.",
            FutureWarning,
        )

    formats = self.formats or self.default_jupytext_formats
    for fmt in long_form_multiple_formats(formats):
        try:
            base_path(path, fmt)
            return formats
        except InconsistentPath:
            continue

    return None
</t>
<t tx="ekr.20241206101345.179">def __eq__(self, other):
    for key in self.class_trait_names():
        if getattr(self, key) != getattr(other, key):
            return False

    return True
</t>
<t tx="ekr.20241206101345.18">@language python

import pandas as pd  # A comment

def spam(s: str) -&gt; str:
    return 'hi'
    
@language md

# Loading and Analyzing Data
# ==========================

*Italics* and **bold**

More markdown. 
</t>
<t tx="ekr.20241206101345.180">def preferred_format(incomplete_format, preferred_formats):
    """Return the preferred format for the given extension"""
    incomplete_format = long_form_one_format(incomplete_format)
    if "format_name" in incomplete_format:
        return incomplete_format

    for fmt in long_form_multiple_formats(preferred_formats):
        if (
            (
                incomplete_format["extension"] == fmt["extension"]
                or (
                    fmt["extension"] == ".auto"
                    and incomplete_format["extension"]
                    not in [".md", ".markdown", ".Rmd", ".ipynb"]
                )
            )
            and incomplete_format.get("suffix")
            == fmt.get("suffix", incomplete_format.get("suffix"))
            and incomplete_format.get("prefix")
            == fmt.get("prefix", incomplete_format.get("prefix"))
        ):
            fmt.update(incomplete_format)
            return fmt

    return incomplete_format
</t>
<t tx="ekr.20241206101345.181">def global_jupytext_configuration_directories():
    """Return the directories in which Jupytext will search for a configuration file"""

    config_dirs = []

    if "XDG_CONFIG_HOME" in os.environ:
        config_dirs.extend(os.environ["XDG_CONFIG_HOME"].split(":"))
    elif "USERPROFILE" in os.environ:
        config_dirs.append(os.environ["USERPROFILE"])
    elif "HOME" in os.environ:
        config_dirs.append(os.path.join(os.environ["HOME"], ".config"))
        config_dirs.append(os.environ["HOME"])

    if "XDG_CONFIG_DIRS" in os.environ:
        config_dirs.extend(os.environ["XDG_CONFIG_DIRS"].split(":"))
    elif "ALLUSERSPROFILE" in os.environ:
        config_dirs.append(os.environ["ALLUSERSPROFILE"])
    else:
        config_dirs.extend(["/usr/local/share/", "/usr/share/"])

    for config_dir in config_dirs:
        yield from [
            os.path.join(config_dir, "jupytext"),
            config_dir,
        ]
</t>
<t tx="ekr.20241206101345.182">def find_global_jupytext_configuration_file():
    """Return the global Jupytext configuration file, if any"""

    for config_dir in global_jupytext_configuration_directories():
        config_file = find_jupytext_configuration_file(config_dir, False)
        if config_file:
            return config_file

    return None
</t>
<t tx="ekr.20241206101345.183">def find_jupytext_configuration_file(path, search_parent_dirs=True):
    """Return the first jupytext configuration file in the current directory, or any parent directory"""
    if os.path.isdir(path):
        for filename in JUPYTEXT_CONFIG_FILES:
            full_path = os.path.join(path, filename)
            if os.path.isfile(full_path):
                return full_path

    pyproject_path = os.path.join(path, PYPROJECT_FILE)
    if os.path.isfile(pyproject_path):
        with open(pyproject_path) as stream:
            doc = tomllib.loads(stream.read())
            if doc.get("tool", {}).get("jupytext") is not None:
                return pyproject_path

    if not search_parent_dirs:
        return None

    if JUPYTEXT_CEILING_DIRECTORIES and os.path.isdir(path):
        for ceiling_dir in JUPYTEXT_CEILING_DIRECTORIES:
            if os.path.isdir(ceiling_dir) and os.path.samefile(path, ceiling_dir):
                return None

    parent_dir = os.path.dirname(path)
    if parent_dir == path:
        return find_global_jupytext_configuration_file()

    return find_jupytext_configuration_file(parent_dir)
</t>
<t tx="ekr.20241206101345.184">def parse_jupytext_configuration_file(jupytext_config_file, stream=None):
    """Read a Jupytext config file, and return a dict"""
    if not jupytext_config_file.endswith(".py") and stream is None:
        with open(jupytext_config_file, encoding="utf-8") as stream:
            return parse_jupytext_configuration_file(
                jupytext_config_file, stream.read()
            )

    try:
        if jupytext_config_file.endswith((".toml", "jupytext")):
            doc = tomllib.loads(stream)
            if jupytext_config_file.endswith(PYPROJECT_FILE):
                return doc["tool"]["jupytext"]
            else:
                return doc

        if jupytext_config_file.endswith((".yml", ".yaml")):
            return yaml.safe_load(stream)

        if jupytext_config_file.endswith(".json"):
            return json.loads(stream)

        return PyFileConfigLoader(jupytext_config_file).load_config()
    except (ValueError, NameError) as err:
        raise JupytextConfigurationError(
            "The Jupytext configuration file {} is incorrect: {}".format(
                jupytext_config_file, err
            )
        )
</t>
<t tx="ekr.20241206101345.185">def load_jupytext_configuration_file(config_file, stream=None):
    """Read and validate a Jupytext configuration file, and return a JupytextConfiguration object"""
    config_dict = parse_jupytext_configuration_file(config_file, stream)
    config = validate_jupytext_configuration_file(config_file, config_dict)
    # formats can be a dict prefix =&gt; format
    if isinstance(config.formats, dict):
        config.formats = [
            fmt
            if not prefix
            else (prefix[:-1] if prefix.endswith("/") else prefix) + "///" + fmt
            for prefix, fmt in config.formats.items()
        ]
    config.formats = short_form_multiple_formats(config.formats)
    if isinstance(config.notebook_extensions, str):
        config.notebook_extensions = config.notebook_extensions.split(",")
    return config
</t>
<t tx="ekr.20241206101345.186">def load_jupytext_config(nb_file):
    """Return the jupytext configuration file in the same folder, or in a parent folder, of the current file, if any"""
    config_file = find_jupytext_configuration_file(nb_file)
    if config_file is None:
        return None
    if os.path.isfile(nb_file) and os.path.samefile(config_file, nb_file):
        return None
    config_file = find_jupytext_configuration_file(nb_file)
    return load_jupytext_configuration_file(config_file)
</t>
<t tx="ekr.20241206101345.187">def validate_jupytext_configuration_file(config_file, config_dict):
    """Turn a dict-like config into a JupytextConfiguration object"""
    if config_dict is None:
        return None
    try:
        config = JupytextConfiguration(**config_dict)
    except TraitError as err:
        raise JupytextConfigurationError(
            "The Jupytext configuration file {} is incorrect: {}".format(
                config_file, err
            )
        )
    invalid_options = set(config_dict).difference(dir(JupytextConfiguration()))
    if any(invalid_options):
        raise JupytextConfigurationError(
            "The Jupytext configuration file {} is incorrect: options {} are not supported".format(
                config_file, ",".join(invalid_options)
            )
        )
    return config
</t>
<t tx="ekr.20241206101345.188">def notebook_formats(nbk, config, path, fallback_on_current_fmt=True):
    """Return the list of formats for the current notebook"""
    metadata = nbk.get("metadata")
    jupytext_metadata = metadata.get("jupytext", {})
    formats = (
        jupytext_metadata.get("formats")
        or metadata.get("jupytext_formats")
        or (config.default_formats(path) if config else None)
    )

    if not formats:
        if not fallback_on_current_fmt:
            return None
        text_repr = jupytext_metadata.get("text_representation", {})
        ext = os.path.splitext(path)[1]
        fmt = {"extension": ext}

        if ext == text_repr.get("extension") and text_repr.get("format_name"):
            fmt["format_name"] = text_repr.get("format_name")

        formats = [fmt]

    formats = long_form_multiple_formats(
        formats, metadata, auto_ext_requires_language_info=False
    )

    # Set preferred formats if no format name has been given yet
    if config:
        formats = [
            preferred_format(f, config.preferred_jupytext_formats_save) for f in formats
        ]

    return formats
</t>
<t tx="ekr.20241206101345.189">"""ContentsManager that allows to open Rmd, py, R and ipynb files as notebooks
"""
import itertools
import os

try:
    import tomllib
except ImportError:
    import tomli as tomllib

from collections import namedtuple
from datetime import timedelta

import nbformat
from tornado.web import HTTPError

# import notebook.transutils before notebook.services.contents.filemanager #75
try:
    import notebook.transutils  # noqa
except ImportError:
    pass

from .config import (
    JUPYTEXT_CONFIG_FILES,
    PYPROJECT_FILE,
    JupytextConfiguration,
    JupytextConfigurationError,
    find_global_jupytext_configuration_file,
    load_jupytext_configuration_file,
    notebook_formats,
    preferred_format,
)
from .formats import (
    long_form_multiple_formats,
    short_form_multiple_formats,
    short_form_one_format,
)
from .jupytext import drop_text_representation_metadata, reads, writes
from .kernels import set_kernelspec_from_language
from .paired_paths import (
    InconsistentPath,
    base_path,
    find_base_path_and_format,
    full_path,
    paired_paths,
)
from .pairs import PairedFilesDiffer, latest_inputs_and_outputs, read_pair, write_pair


@others
    return JupytextContentsManager


try:
    # The LargeFileManager is taken by default from jupyter_server if available
    from jupyter_server.services.contents.largefilemanager import LargeFileManager

    TextFileContentsManager = build_jupytext_contents_manager_class(LargeFileManager)
except ImportError:
    # If we can't find jupyter_server then we take it from notebook
    try:
        from notebook.services.contents.largefilemanager import LargeFileManager

        TextFileContentsManager = build_jupytext_contents_manager_class(
            LargeFileManager
        )
    except ImportError:
        # Older versions of notebook do not have the LargeFileManager #217
        from notebook.services.contents.filemanager import FileContentsManager

        TextFileContentsManager = build_jupytext_contents_manager_class(
            FileContentsManager
        )
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.19">@language cweb

% This is limbo in cweb mode... It should be in \LaTeX mode, not \c mode.
% The following should not be colorized: class,if,else.

@* this is a _cweb_ comment.  Code is written in \c.
"strings" should not be colorized.
It should be colored in \LaTeX mode.
The following are not keywords in latex mode: if, else, etc.
Noweb section references are _valid_ in cweb comments!
&lt; &lt; section ref &gt; &gt;
&lt; &lt; missing ref &gt; &gt;
@c

and this is C code. // It is colored in \LaTeX mode by default.
/* This is a C block comment.  It may also be colored in restricted \LaTeX mode. */

// Section refs are valid in code too, of course.
&lt; &lt; section ref &gt; &gt;
&lt; &lt; missing ref &gt; &gt;

\LaTeX and \c should not be colored.
if else, while, do // C keywords.
</t>
<t tx="ekr.20241206101345.190">def build_jupytext_contents_manager_class(base_contents_manager_class):
@others
</t>
<t tx="ekr.20241206101345.191">    """Derives a TextFileContentsManager class from the given base class"""

    class JupytextContentsManager(base_contents_manager_class, JupytextConfiguration):
            """
            A FileContentsManager Class that reads and stores notebooks to classical
            Jupyter notebooks (.ipynb), R Markdown notebooks (.Rmd), Julia (.jl),
            Python (.py) or R scripts (.R)
            """
@others
</t>
<t tx="ekr.20241206101345.192">        def __init__(self, *args, **kwargs):
            # Dictionary: notebook path =&gt; (fmt, formats) where
            # fmt is the current format, and formats the paired formats.
            self.paired_notebooks = dict()

            # Configuration cache, useful when notebooks are listed in a given directory
            self.cached_config = namedtuple("cached_config", "path config_file config")
            self.super = super()
            self.super.__init__(*args, **kwargs)
</t>
<t tx="ekr.20241206101345.193">        def all_nb_extensions(self, config):
            """All extensions that should be classified as notebooks"""
            return [
                ext if ext.startswith(".") else "." + ext
                for ext in config.notebook_extensions
            ]
</t>
<t tx="ekr.20241206101345.194">        def drop_paired_notebook(self, path):
            """Remove the current notebook from the list of paired notebooks"""
            if path not in self.paired_notebooks:
                return

            fmt, formats = self.paired_notebooks.pop(path)
            prev_paired_paths = paired_paths(path, fmt, formats)
            for alt_path, _ in prev_paired_paths:
                if alt_path in self.paired_notebooks:
                    self.drop_paired_notebook(alt_path)
</t>
<t tx="ekr.20241206101345.195">        def update_paired_notebooks(self, path, formats):
            """Update the list of paired notebooks to include/update the current pair"""
            if not formats:
                self.drop_paired_notebook(path)
                return

            formats = long_form_multiple_formats(formats)
            _, fmt = find_base_path_and_format(path, formats)
            new_paired_paths = paired_paths(path, fmt, formats)
            for alt_path, _ in new_paired_paths:
                self.drop_paired_notebook(alt_path)

            if len(formats) == 1 and set(formats[0]) &lt;= {"extension"}:
                return

            short_formats = short_form_multiple_formats(formats)
            for alt_path, alt_fmt in new_paired_paths:
                self.paired_notebooks[alt_path] = (
                    short_form_one_format(alt_fmt),
                    short_formats,
                )
</t>
<t tx="ekr.20241206101345.196">        def create_prefix_dir(self, path, fmt):
            """Create the prefix dir, if missing"""
            if "prefix" in fmt and "/" in path:
                parent_dir = self.get_parent_dir(path)
                if not self.dir_exists(parent_dir):
                    self.create_prefix_dir(parent_dir, fmt)
                    self.log.info("Creating directory %s", parent_dir)
                    self.super.save(dict(type="directory"), parent_dir)
</t>
<t tx="ekr.20241206101345.197">        def save(self, model, path=""):
            """Save the file model and return the model with no content."""
            if model["type"] != "notebook":
                return self.super.save(model, path)

            path = path.strip("/")
            nbk = model["content"]
            try:
                config = self.get_config(path)
                jupytext_formats = notebook_formats(nbk, config, path)
                self.update_paired_notebooks(path, jupytext_formats)

                def save_one_file(path, fmt):
                    if "format_name" in fmt and fmt["extension"] not in [
                        ".md",
                        ".markdown",
                        ".Rmd",
                    ]:
                        self.log.info(
                            "Saving %s in format %s:%s",
                            os.path.basename(path),
                            fmt["extension"][1:],
                            fmt["format_name"],
                        )
                    else:
                        self.log.info("Saving %s", os.path.basename(path))

                    self.create_prefix_dir(path, fmt)
                    if fmt["extension"] == ".ipynb":
                        return self.super.save(
                            dict(
                                type="notebook",
                                content=drop_text_representation_metadata(
                                    model["content"]
                                ),
                            ),
                            path,
                        )

                    if (
                        model["content"]["metadata"]
                        .get("jupytext", {})
                        .get("notebook_metadata_filter")
                        == "-all"
                    ):
                        self.log.warning(
                            "Stripping metadata from {} as 'Include Metadata' is off "
                            "(toggle 'Include Metadata' in the Jupytext Menu or Commands if desired)".format(
                                path
                            )
                        )

                    text_model = dict(
                        type="file",
                        format="text",
                        content=writes(
                            nbformat.from_dict(model["content"]), fmt=fmt, config=config
                        ),
                    )

                    return self.super.save(text_model, path)

                return write_pair(path, jupytext_formats, save_one_file)

            except Exception as e:
                self.log.error("Error while saving file: %s %s", path, e, exc_info=True)
                raise HTTPError(500, f"Unexpected error while saving file: {path} {e}")
</t>
<t tx="ekr.20241206101345.198">        def get(
            self,
            path,
            content=True,
            type=None,
            format=None,
            load_alternative_format=True,
        ):
            """Takes a path for an entity and returns its model"""
            path = path.strip("/")
            ext = os.path.splitext(path)[1]

            # Not a notebook?
            if (
                not self.file_exists(path)
                or self.dir_exists(path)
                or (type is not None and type != "notebook")
            ):
                return self.super.get(path, content, type, format)

            config = self.get_config(path, use_cache=content is False)
            if ext not in self.all_nb_extensions(config):
                return self.super.get(path, content, type, format)

            fmt = preferred_format(ext, config.preferred_jupytext_formats_read)
            if ext == ".ipynb":
                model = self.super.get(path, content, type="notebook", format=format)
            else:
                model = self.super.get(path, content, type="file", format="text")
                model["type"] = "notebook"
                if content:
                    # We may need to update these keys, inherited from text files formats
                    # Cf. https://github.com/mwouts/jupytext/issues/659
                    model["format"] = "json"
                    model["mimetype"] = None
                    try:
                        model["content"] = reads(
                            model["content"], fmt=fmt, config=config
                        )
                        # mark all code cells from text notebooks as 'trusted'
                        # as they don't have any outputs, cf. #941
                        for cell in model["content"].cells:
                            if cell.cell_type == "code":
                                cell["metadata"]["trusted"] = True

                    except Exception as err:
                        self.log.error(
                            "Error while reading file: %s %s", path, err, exc_info=True
                        )
                        raise HTTPError(500, str(err))

            if not load_alternative_format:
                return model

            # We will now read a second file if this is a paired notebooks.
            if content:
                nbk = model["content"]
                formats = nbk.metadata.get("jupytext", {}).get(
                    "formats"
                ) or config.default_formats(path)
                formats = long_form_multiple_formats(
                    formats, nbk.metadata, auto_ext_requires_language_info=False
                )
            else:
                if path not in self.paired_notebooks:
                    return model

                _, formats = self.paired_notebooks.get(path)
                formats = long_form_multiple_formats(formats)

            # Compute paired notebooks from formats
            alt_paths = [(path, fmt)]
            if formats:
                try:
                    _, fmt = find_base_path_and_format(path, formats)
                    alt_paths = paired_paths(path, fmt, formats)
                    self.update_paired_notebooks(path, formats)
                except InconsistentPath as err:
                    self.log.error(
                        "Unable to read paired notebook: %s %s",
                        path,
                        err,
                        exc_info=True,
                    )
            else:
                if path in self.paired_notebooks:
                    fmt, formats = self.paired_notebooks.get(path)
                    alt_paths = paired_paths(path, fmt, formats)
                    formats = long_form_multiple_formats(formats)

            if content and len(alt_paths) &gt; 1 and ext == ".ipynb":
                # Apply default options (like saving and reloading would do)
                jupytext_metadata = model["content"]["metadata"].get("jupytext", {})
                config.set_default_format_options(jupytext_metadata, read=True)
                if jupytext_metadata:
                    model["content"]["metadata"]["jupytext"] = jupytext_metadata

            def get_timestamp(alt_path):
                if not self.exists(alt_path):
                    return None
                if alt_path == path:
                    return model["last_modified"]
                return self.super.get(alt_path, content=False)["last_modified"]

            def read_one_file(alt_path, alt_fmt):
                if alt_path == path:
                    return model["content"]
                if alt_path.endswith(".ipynb"):
                    self.log.info(f"Reading OUTPUTS from {alt_path}")
                    return self.super.get(
                        alt_path, content=True, type="notebook", format=format
                    )["content"]

                self.log.info(f"Reading SOURCE from {alt_path}")
                text = self.super.get(
                    alt_path, content=True, type="file", format=format
                )["content"]
                return reads(text, fmt=alt_fmt, config=config)

            inputs, outputs = latest_inputs_and_outputs(
                path, fmt, formats, get_timestamp, contents_manager_mode=True
            )

            # Modification time of a paired notebook is the timestamp of inputs #118 #978
            model["last_modified"] = inputs.timestamp

            if not content:
                return model

            # Before we combine the two files, we make sure we're not overwriting ipynb cells
            # with an outdated text file
            content = None
            try:
                if (
                    outputs.timestamp
                    and outputs.timestamp
                    &gt; inputs.timestamp
                    + timedelta(seconds=config.outdated_text_notebook_margin)
                ):
                    ts_mismatch = (
                        "{out} (last modified {out_last}) is more recent than "
                        "{src} (last modified {src_last})".format(
                            src=inputs.path,
                            src_last=inputs.timestamp,
                            out=outputs.path,
                            out_last=outputs.timestamp,
                        )
                    )
                    self.log.warning(ts_mismatch)

                    try:
                        content = read_pair(
                            inputs, outputs, read_one_file, must_match=True
                        )
                        self.log.warning(
                            "The inputs in {src} and {out} are identical, "
                            "so the mismatch in timestamps was ignored".format(
                                src=inputs.path, out=outputs.path
                            )
                        )
                    except HTTPError:
                        raise
                    except PairedFilesDiffer as diff:
                        raise HTTPError(
                            400,
                            """{ts_mismatch}

Differences (jupytext --diff {src} {out}) are:
{diff}
Please either:
- open {src} in a text editor, make sure it is up to date, and save it,
- or delete {src} if not up to date,
- or increase check margin by adding, say,
outdated_text_notebook_margin = 5  # default is 1 (second)
to your jupytext.toml file
</t>
<t tx="ekr.20241206101345.199">                        """.format(
                                ts_mismatch=ts_mismatch,
                                src=inputs.path,
                                out=outputs.path,
                                diff=diff,
                            ),
                        )
            except OverflowError:
                pass

            if content is not None:
                model["content"] = content
            else:
                try:
                    model["content"] = read_pair(inputs, outputs, read_one_file)
                except HTTPError:
                    raise
                except Exception as err:
                    self.log.error(
                        "Error while reading file: %s %s", path, err, exc_info=True
                    )
                    raise HTTPError(500, str(err))

            if not outputs.timestamp:
                set_kernelspec_from_language(model["content"])

            return model

        def new_untitled(self, path="", type="", ext=""):
            """Create a new untitled file or directory in path

            We override the base function because that one does not take the 'ext' argument
            into account when type=="notebook". See https://github.com/mwouts/jupytext/issues/443
            """
            if type != "notebook" and ext != ".ipynb":
                return self.super.new_untitled(path, type, ext)

            ext = ext or ".ipynb"
            if ":" in ext:
                ext, format_name = ext.split(":", 1)
            else:
                format_name = ""

            path = path.strip("/")
            if not self.dir_exists(path):
                raise HTTPError(404, "No such directory: %s" % path)

            untitled = self.untitled_notebook
            config = self.get_config(path)
            name = self.increment_notebook_filename(config, untitled + ext, path)
            path = f"{path}/{name}"

            model = {"type": "notebook"}
            if format_name:
                model["format"] = "json"
                model["content"] = nbformat.v4.nbbase.new_notebook(
                    metadata={"jupytext": {"formats": ext + ":" + format_name}}
                )

            return self.new(model, path)
</t>
<t tx="ekr.20241206101345.2"></t>
<t tx="ekr.20241206101345.20">@language elisp

; Maybe...
error princ

; More typical of other lisps...
and apply
car cdr cons cond
defconst defun defvar
eq equal eval
gt ge
if
let le lt
mapcar
ne nil
or not
prog progn
set setq
t type-of
unless
when while</t>
<t tx="ekr.20241206101345.200">        def increment_notebook_filename(self, config, filename, path=""):
            """Increment a notebook filename until it is unique, regardless of extension"""
            # Extract the full suffix from the filename (e.g. .tar.gz)
            path = path.strip("/")
            basename, dot, ext = filename.partition(".")
            ext = dot + ext

            for i in itertools.count():
                if i:
                    insert_i = f"{i}"
                else:
                    insert_i = ""
                basename_i = basename + insert_i
                name = basename_i + ext
                if not any(
                    self.exists(f"{path}/{basename_i}{nb_ext}")
                    for nb_ext in config.notebook_extensions
                ):
                    break
            return name
</t>
<t tx="ekr.20241206101345.201">        def trust_notebook(self, path):
            """Trust the current notebook"""
            if path.endswith(".ipynb") or path not in self.paired_notebooks:
                self.super.trust_notebook(path)
                return

            fmt, formats = self.paired_notebooks[path]
            for alt_path, alt_fmt in paired_paths(path, fmt, formats):
                if alt_fmt["extension"] == ".ipynb":
                    self.super.trust_notebook(alt_path)
</t>
<t tx="ekr.20241206101345.202">        def rename_file(self, old_path, new_path):
            """Rename the current notebook, as well as its alternative representations"""
            if old_path not in self.paired_notebooks:
                try:
                    # we do not know yet if this is a paired notebook (#190)
                    # -&gt; to get this information we open the notebook
                    self.get(old_path, content=True)
                except Exception:
                    pass

            if old_path not in self.paired_notebooks:
                self.super.rename_file(old_path, new_path)
                return

            fmt, formats = self.paired_notebooks.get(old_path)
            old_alt_paths = paired_paths(old_path, fmt, formats)

            # Is the new file name consistent with suffix?
            try:
                new_base = base_path(new_path, fmt)
            except HTTPError:
                raise
            except Exception as err:
                self.log.error(
                    "Error while renaming file from %s to %s: %s",
                    old_path,
                    new_path,
                    err,
                    exc_info=True,
                )
                raise HTTPError(500, str(err))

            for old_alt_path, alt_fmt in old_alt_paths:
                new_alt_path = full_path(new_base, alt_fmt)
                if self.exists(old_alt_path):
                    self.create_prefix_dir(new_alt_path, alt_fmt)
                    self.super.rename_file(old_alt_path, new_alt_path)

            self.drop_paired_notebook(old_path)
            self.update_paired_notebooks(new_path, formats)
</t>
<t tx="ekr.20241206101345.203">        def get_parent_dir(self, path):
            """The parent directory"""
            if "/" in path:
                return path.rsplit("/", 1)[0]
            # jupyter-fs
            if ":" in path and hasattr(self, "_managers"):
                if path.endswith(":"):
                    return ""
                return path.rsplit(":", 1)[0] + ":"
            return ""
</t>
<t tx="ekr.20241206101345.204">        def get_config_file(self, directory):
            """Return the jupytext configuration file, if any"""
            for jupytext_config_file in JUPYTEXT_CONFIG_FILES:
                path = directory + "/" + jupytext_config_file
                if self.file_exists(path):
                    if not self.allow_hidden and jupytext_config_file.startswith("."):
                        self.log.warning(
                            f"Ignoring config file {path} (see Jupytext issue #964)"
                        )
                        continue
                    return path

            pyproject_path = directory + "/" + PYPROJECT_FILE
            if self.file_exists(pyproject_path):
                model = self.get(pyproject_path, type="file")
                try:
                    doc = tomllib.loads(model["content"])
                except tomllib.TOMLDecodeError as e:
                    self.log.warning(f"Cannot load {pyproject_path}: {e}")
                else:
                    if doc.get("tool", {}).get("jupytext") is not None:
                        return pyproject_path

            if not directory:
                return None

            parent_dir = self.get_parent_dir(directory)
            return self.get_config_file(parent_dir)
</t>
<t tx="ekr.20241206101345.205">        def load_config_file(
            self, config_file, *, prev_config_file, prev_config, is_os_path=False
        ):
            """Load the configuration file"""
            if config_file is None:
                return None
            if config_file.endswith(".py") and not is_os_path:
                config_file = self._get_os_path(config_file)
                is_os_path = True

            config_content = None
            if not is_os_path:
                try:
                    model = self.super.get(config_file, content=True, type="file")
                    config_content = model["content"]
                except HTTPError:
                    pass

            config = load_jupytext_configuration_file(config_file, config_content)
            if config is None:
                return config

            log_level = config.cm_config_log_level
            if log_level == "info_if_changed":
                if config_file != prev_config_file or config != prev_config:
                    log_level = "info"
                else:
                    log_level = "none"
            if log_level != "none":
                getattr(self.log, log_level)(
                    "Loaded Jupytext configuration file at %s", config_file
                )
            return config
</t>
<t tx="ekr.20241206101345.206">        def get_config(self, path, use_cache=False):
            """Return the Jupytext configuration for the given path"""
            parent_dir = self.get_parent_dir(path)

            # When listing the notebooks for the tree view, we use a cache for the configuration file
            # The cache will be refreshed when a notebook is opened or saved, or when we go
            # to a different directory.
            if not use_cache or parent_dir != self.cached_config.path:
                try:
                    config_file = self.get_config_file(parent_dir)
                    if config_file:
                        self.cached_config.config = self.load_config_file(
                            config_file,
                            prev_config_file=self.cached_config.config_file,
                            prev_config=self.cached_config.config,
                        )
                    else:
                        config_file = find_global_jupytext_configuration_file()
                        self.cached_config.config = self.load_config_file(
                            config_file,
                            prev_config_file=self.cached_config.config_file,
                            prev_config=self.cached_config.config,
                            is_os_path=True,
                        )
                    self.cached_config.config_file = config_file
                    self.cached_config.path = parent_dir
                except JupytextConfigurationError as err:
                    self.log.error(
                        "Error while reading config file: %s %s",
                        config_file,
                        err,
                        exc_info=True,
                    )
                    raise HTTPError(500, f"{err}")

            if self.cached_config.config is not None:
                return self.cached_config.config
            if isinstance(self.notebook_extensions, str):
                self.notebook_extensions = self.notebook_extensions.split(",")
            return self
</t>
<t tx="ekr.20241206101345.207">"""Convert Markdown equations to doxygen equations and back
See https://github.com/mwouts/jupytext/issues/517"""

import re


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.208">def markdown_to_doxygen(string):
    """Markdown to Doxygen equations"""
    long_equations = re.sub(
        r"(?&lt;!\\)\$\$(.*?)(?&lt;!\\)\$\$", r"\\f[\g&lt;1&gt;\\f]", string, flags=re.DOTALL
    )
    inline_equations = re.sub(r"(?&lt;!(\\|\$))\$(?!\$)", r"\\f$", long_equations)
    return inline_equations
</t>
<t tx="ekr.20241206101345.209">def doxygen_to_markdown(string):
    """Doxygen to Markdown equations"""
    long_equations = re.sub(r"\\f\[(.*?)\\f\]", r"$$\g&lt;1&gt;$$", string, flags=re.DOTALL)
    inline_equations = re.sub(r"\\f\$", "$", long_equations)
    return inline_equations
</t>
<t tx="ekr.20241206101345.21"></t>
<t tx="ekr.20241206101345.210">"""
In this file the various text notebooks formats are defined. Please contribute
new formats here!
"""

import os
import re
import warnings

import nbformat
import yaml

from .cell_reader import (
    DoublePercentScriptCellReader,
    HydrogenCellReader,
    LightScriptCellReader,
    MarkdownCellReader,
    RMarkdownCellReader,
    RScriptCellReader,
    SphinxGalleryScriptCellReader,
)
from .cell_to_text import (
    BareScriptCellExporter,
    DoublePercentCellExporter,
    HydrogenCellExporter,
    LightScriptCellExporter,
    MarkdownCellExporter,
    RMarkdownCellExporter,
    RScriptCellExporter,
    SphinxGalleryCellExporter,
)
from .header import header_to_metadata_and_cell, insert_or_test_version_number
from .languages import _COMMENT_CHARS, _SCRIPT_EXTENSIONS, same_language
from .magics import is_magic
from .metadata_filter import metadata_filter_as_string
from .myst import (
    MYST_FORMAT_NAME,
    is_myst_available,
    matches_mystnb,
    myst_extensions,
    myst_version,
)
from .pandoc import is_pandoc_available, pandoc_version
from .stringparser import StringParser
from .version import __version__


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.211">class JupytextFormatError(ValueError):
    """Error in the specification of the format for the text notebook"""
</t>
<t tx="ekr.20241206101345.212">class NotebookFormatDescription:
    """Description of a notebook format"""
    @others
</t>
<t tx="ekr.20241206101345.213">def __init__(
    self,
    format_name,
    extension,
    header_prefix,
    cell_reader_class,
    cell_exporter_class,
    current_version_number,
    header_suffix="",
    min_readable_version_number=None,
):
    self.format_name = format_name
    self.extension = extension
    self.header_prefix = header_prefix
    self.header_suffix = header_suffix
    self.cell_reader_class = cell_reader_class
    self.cell_exporter_class = cell_exporter_class
    self.current_version_number = current_version_number
    self.min_readable_version_number = min_readable_version_number
</t>
<t tx="ekr.20241206101345.214">JUPYTEXT_FORMATS = (
    [
        NotebookFormatDescription(
            format_name="markdown",
            extension=".md",
            header_prefix="",
            cell_reader_class=MarkdownCellReader,
            cell_exporter_class=MarkdownCellExporter,
            # Version 1.0 on 2018-08-31 - jupytext v0.6.0 : Initial version
            # Version 1.1 on 2019-03-24 - jupytext v1.1.0 : Markdown regions and cell metadata
            # Version 1.2 on 2019-09-21 - jupytext v1.3.0 : Raw regions are now encoded with HTML comments (#321)
            # and by default, cell metadata use the key=value representation (#347)
            # Version 1.3 on 2021-01-24 - jupytext v1.10.0 : Code cells may start with more than three backticks (#712)
            current_version_number="1.3",
            min_readable_version_number="1.0",
        ),
        NotebookFormatDescription(
            format_name="markdown",
            extension=".markdown",
            header_prefix="",
            cell_reader_class=MarkdownCellReader,
            cell_exporter_class=MarkdownCellExporter,
            current_version_number="1.2",
            min_readable_version_number="1.0",
        ),
        NotebookFormatDescription(
            format_name="rmarkdown",
            extension=".Rmd",
            header_prefix="",
            cell_reader_class=RMarkdownCellReader,
            cell_exporter_class=RMarkdownCellExporter,
            # Version 1.0 on 2018-08-22 - jupytext v0.5.2 : Initial version
            # Version 1.1 on 2019-03-24 - jupytext v1.1.0 : Markdown regions and cell metadata
            # Version 1.2 on 2019-09-21 - jupytext v1.3.0 : Raw regions are now encoded with HTML comments (#321)
            # and by default, cell metadata use the key=value representation in raw and markdown cells (#347)
            current_version_number="1.2",
            min_readable_version_number="1.0",
        ),
    ]
    + [
        NotebookFormatDescription(
            format_name="light",
            extension=ext,
            header_prefix=_SCRIPT_EXTENSIONS[ext]["comment"],
            header_suffix=_SCRIPT_EXTENSIONS[ext].get("comment_suffix", ""),
            cell_reader_class=LightScriptCellReader,
            cell_exporter_class=LightScriptCellExporter,
            # Version 1.5 on 2019-10-19 - jupytext v1.3.0 - Cell metadata represented as key=value by default
            # Version 1.4 on 2019-03-30 - jupytext v1.1.0 - custom cell markers allowed
            # Version 1.3 on 2018-09-22 - jupytext v0.7.0rc0 : Metadata are
            # allowed for all cell types (and then include 'cell_type')
            # Version 1.2 on 2018-09-05 - jupytext v0.6.3 : Metadata bracket
            # can be omitted when empty, if previous line is empty #57
            # Version 1.1 on 2018-08-25 - jupytext v0.6.0 : Cells separated
            # with one blank line #38
            # Version 1.0 on 2018-08-22 - jupytext v0.5.2 : Initial version
            current_version_number="1.5",
            min_readable_version_number="1.1",
        )
        for ext in _SCRIPT_EXTENSIONS
    ]
    + [
        NotebookFormatDescription(
            format_name="nomarker",
            extension=ext,
            header_prefix=_SCRIPT_EXTENSIONS[ext]["comment"],
            header_suffix=_SCRIPT_EXTENSIONS[ext].get("comment_suffix", ""),
            cell_reader_class=LightScriptCellReader,
            cell_exporter_class=BareScriptCellExporter,
            current_version_number="1.0",
            min_readable_version_number="1.0",
        )
        for ext in _SCRIPT_EXTENSIONS
    ]
    + [
        NotebookFormatDescription(
            format_name="percent",
            extension=ext,
            header_prefix=_SCRIPT_EXTENSIONS[ext]["comment"],
            header_suffix=_SCRIPT_EXTENSIONS[ext].get("comment_suffix", ""),
            cell_reader_class=DoublePercentScriptCellReader,
            cell_exporter_class=DoublePercentCellExporter,
            # Version 1.3 on 2019-09-21 - jupytext v1.3.0: Markdown cells can be quoted using triple quotes #305
            # and cell metadata are represented as key=value by default
            # Version 1.2 on 2018-11-18 - jupytext v0.8.6: Jupyter magics are commented by default #126, #132
            # Version 1.1 on 2018-09-23 - jupytext v0.7.0rc1 : [markdown] and
            # [raw] for markdown and raw cells.
            # Version 1.0 on 2018-09-22 - jupytext v0.7.0rc0 : Initial version
            current_version_number="1.3",
            min_readable_version_number="1.1",
        )
        for ext in _SCRIPT_EXTENSIONS
    ]
    + [
        NotebookFormatDescription(
            format_name="hydrogen",
            extension=ext,
            header_prefix=_SCRIPT_EXTENSIONS[ext]["comment"],
            header_suffix=_SCRIPT_EXTENSIONS[ext].get("comment_suffix", ""),
            cell_reader_class=HydrogenCellReader,
            cell_exporter_class=HydrogenCellExporter,
            # Version 1.2 on 2018-12-14 - jupytext v0.9.0: same as percent - only magics are not commented by default
            current_version_number="1.3",
            min_readable_version_number="1.1",
        )
        for ext in _SCRIPT_EXTENSIONS
    ]
    + [
        NotebookFormatDescription(
            format_name="spin",
            extension=ext,
            header_prefix="#'",
            cell_reader_class=RScriptCellReader,
            cell_exporter_class=RScriptCellExporter,
            # Version 1.0 on 2018-08-22 - jupytext v0.5.2 : Initial version
            current_version_number="1.0",
        )
        for ext in [".r", ".R"]
    ]
    + [
        NotebookFormatDescription(
            format_name="sphinx",
            extension=".py",
            header_prefix="#",
            cell_reader_class=SphinxGalleryScriptCellReader,
            cell_exporter_class=SphinxGalleryCellExporter,
            # Version 1.0 on 2018-09-22 - jupytext v0.7.0rc0 : Initial version
            current_version_number="1.1",
        ),
        NotebookFormatDescription(
            format_name="pandoc",
            extension=".md",
            header_prefix="",
            cell_reader_class=None,
            cell_exporter_class=None,
            current_version_number=pandoc_version(),
        ),
        NotebookFormatDescription(
            format_name="quarto",
            extension=".qmd",
            header_prefix="",
            cell_reader_class=None,
            cell_exporter_class=None,
            # Version 1.0 on 2021-09-07 = quarto --version &gt;= 0.2.134,
            # cf. https://github.com/mwouts/jupytext/issues/837
            current_version_number="1.0",
        ),
    ]
    + [
        NotebookFormatDescription(
            format_name=MYST_FORMAT_NAME,
            extension=ext,
            header_prefix="",
            cell_reader_class=None,
            cell_exporter_class=None,
            current_version_number=myst_version(),
        )
        for ext in myst_extensions()
    ]
)

NOTEBOOK_EXTENSIONS = list(
    dict.fromkeys([".ipynb"] + [fmt.extension for fmt in JUPYTEXT_FORMATS])
)
EXTENSION_PREFIXES = [".lgt", ".spx", ".pct", ".hyd", ".nb"]


def get_format_implementation(ext, format_name=None):
    """Return the implementation for the desired format"""
    # remove pre-extension if any
    ext = "." + ext.split(".")[-1]

    formats_for_extension = []
    for fmt in JUPYTEXT_FORMATS:
        if fmt.extension == ext:
            if fmt.format_name == format_name or not format_name:
                return fmt
            formats_for_extension.append(fmt.format_name)

    if formats_for_extension:
        raise JupytextFormatError(
            "Format '{}' is not associated to extension '{}'. "
            "Please choose one of: {}.".format(
                format_name, ext, ", ".join(formats_for_extension)
            )
        )
    raise JupytextFormatError(f"No format associated to extension '{ext}'")
</t>
<t tx="ekr.20241206101345.215">def read_metadata(text, ext):
    """Return the header metadata"""
    ext = "." + ext.split(".")[-1]
    lines = text.splitlines()

    if ext in [".md", ".markdown", ".Rmd"]:
        comment = comment_suffix = ""
    else:
        comment = _SCRIPT_EXTENSIONS.get(ext, {}).get("comment", "#")
        comment_suffix = _SCRIPT_EXTENSIONS.get(ext, {}).get("comment_suffix", "")

    metadata, _, _, _ = header_to_metadata_and_cell(lines, comment, comment_suffix, ext)
    if ext in [".r", ".R"] and not metadata:
        metadata, _, _, _ = header_to_metadata_and_cell(lines, "#'", "", ext)

    # MyST has the metadata at the root level
    if not metadata and ext in myst_extensions() and text.startswith("---"):
        for header in yaml.safe_load_all(text):
            if not isinstance(header, dict):
                continue
            if (
                header.get("jupytext", {})
                .get("text_representation", {})
                .get("format_name")
                == "myst"
            ):
                return header
            return metadata

    return metadata
</t>
<t tx="ekr.20241206101345.216">def read_format_from_metadata(text, ext):
    """Return the format of the file, when that information is available from the metadata"""
    metadata = read_metadata(text, ext)
    rearrange_jupytext_metadata(metadata)
    return format_name_for_ext(metadata, ext, explicit_default=False)
</t>
<t tx="ekr.20241206101345.217">def guess_format(text, ext):
    """Guess the format and format options of the file, given its extension and content"""
    metadata = read_metadata(text, ext)

    if "text_representation" in metadata.get("jupytext", {}):
        return format_name_for_ext(metadata, ext), {}

    if is_myst_available() and ext in myst_extensions() and matches_mystnb(text, ext):
        return MYST_FORMAT_NAME, {}

    lines = text.splitlines()

    # Is this a Hydrogen-like script?
    # Or a Sphinx-gallery script?
    if ext in _SCRIPT_EXTENSIONS:
        unescaped_comment = _SCRIPT_EXTENSIONS[ext]["comment"]
        comment = re.escape(unescaped_comment)
        language = _SCRIPT_EXTENSIONS[ext]["language"]
        twenty_hash_re = re.compile(r"^#( |)#{19,}\s*$")
        double_percent_re = re.compile(rf"^{comment}( %%|%%)$")
        double_percent_and_space_re = re.compile(rf"^{comment}( %%|%%)\s")
        nbconvert_script_re = re.compile(rf"^{comment}( &lt;codecell&gt;| In\[[0-9 ]*\]:?)")
        vim_folding_markers_re = re.compile(rf"^{comment}\s*" + "{{{")
        vscode_folding_markers_re = re.compile(rf"^{comment}\s*region")

        twenty_hash_count = 0
        double_percent_count = 0
        magic_command_count = 0
        rspin_comment_count = 0
        vim_folding_markers_count = 0
        vscode_folding_markers_count = 0

        parser = StringParser(language="R" if ext in [".r", ".R"] else "python")
        for line in lines:
            parser.read_line(line)
            if parser.is_quoted():
                continue

            # Don't count escaped Jupyter magics (no space between %% and command) as cells
            if (
                double_percent_re.match(line)
                or double_percent_and_space_re.match(line)
                or nbconvert_script_re.match(line)
            ):
                double_percent_count += 1

            if not line.startswith(unescaped_comment) and is_magic(line, language):
                magic_command_count += 1

            if twenty_hash_re.match(line) and ext == ".py":
                twenty_hash_count += 1

            if line.startswith("#'") and ext in [".R", ".r"]:
                rspin_comment_count += 1

            if vim_folding_markers_re.match(line):
                vim_folding_markers_count += 1

            if vscode_folding_markers_re.match(line):
                vscode_folding_markers_count += 1

        if double_percent_count &gt;= 1:
            if magic_command_count:
                return "hydrogen", {}
            return "percent", {}

        if vim_folding_markers_count:
            return "light", {"cell_markers": "{{{,}}}"}

        if vscode_folding_markers_count:
            return "light", {"cell_markers": "region,endregion"}

        if twenty_hash_count &gt;= 2:
            return "sphinx", {}

        if rspin_comment_count &gt;= 1:
            return "spin", {}

    if ext in [".md", ".markdown"]:
        for line in lines:
            if line.startswith(":::"):  # Pandoc div
                return "pandoc", {}

    # Default format
    return get_format_implementation(ext).format_name, {}
</t>
<t tx="ekr.20241206101345.218">def divine_format(text):
    """Guess the format of the notebook, based on its content #148"""
    try:
        nbformat.reads(text, as_version=4)
        return "ipynb"
    except nbformat.reader.NotJSONError:
        pass

    lines = text.splitlines()
    for comment in ["", "#"] + _COMMENT_CHARS:
        metadata, _, _, _ = header_to_metadata_and_cell(lines, comment, "")
        ext = (
            metadata.get("jupytext", {}).get("text_representation", {}).get("extension")
        )
        if ext:
            return ext[1:] + ":" + guess_format(text, ext)[0]

    # No metadata, but ``` on at least one line =&gt; markdown
    for line in lines:
        if line == "```":
            return "md"

    return "py:" + guess_format(text, ".py")[0]
</t>
<t tx="ekr.20241206101345.219">def check_file_version(notebook, source_path, outputs_path):
    """Raise if file version in source file would override outputs"""
    if not insert_or_test_version_number():
        return

    _, ext = os.path.splitext(source_path)
    assert not ext.endswith(".ipynb"), "source_path={} should be a text file".format(
        source_path
    )

    version = (
        notebook.metadata.get("jupytext", {})
        .get("text_representation", {})
        .get("format_version")
    )
    format_name = format_name_for_ext(notebook.metadata, ext)

    fmt = get_format_implementation(ext, format_name)
    current = fmt.current_version_number

    # Missing version, still generated by jupytext?
    if notebook.metadata and not version:
        version = current

    # Same version? OK
    if version == fmt.current_version_number:
        return

    # Version larger than minimum readable version
    if (fmt.min_readable_version_number or current) &lt;= version &lt;= current:
        return

    jupytext_version_in_file = (
        notebook.metadata.get("jupytext", {})
        .get("text_representation", {})
        .get("jupytext_version", "N/A")
    )

    raise JupytextFormatError(
        "The file {source_path} was generated with jupytext version {jupytext_version_in_file} "
        "but you have {jupytext_version} installed. Please upgrade jupytext to version "
        "{jupytext_version_in_file}, or remove either {source_path} or {output_path}. "
        "This error occurs because {source_path} is in the {format_name} format in version {format_version}, "
        "while jupytext version {jupytext_version} installed at {jupytext_path} can only read the "
        "{format_name} format in versions {min_format_version} to {current_format_version}.".format(
            source_path=os.path.basename(source_path),
            output_path=os.path.basename(outputs_path),
            format_name=format_name,
            format_version=version,
            jupytext_version_in_file=jupytext_version_in_file,
            jupytext_version=__version__,
            jupytext_path=os.path.dirname(os.path.dirname(__file__)),
            min_format_version=fmt.min_readable_version_number or current,
            current_format_version=current,
        )
    )
</t>
<t tx="ekr.20241206101345.22"></t>
<t tx="ekr.20241206101345.220">def format_name_for_ext(metadata, ext, cm_default_formats=None, explicit_default=True):
    """Return the format name for that extension"""
    # Is the format information available in the text representation?
    text_repr = metadata.get("jupytext", {}).get("text_representation", {})
    if text_repr.get("extension", "").endswith(ext) and text_repr.get("format_name"):
        return text_repr.get("format_name")

    # Format from jupytext.formats
    formats = metadata.get("jupytext", {}).get("formats", "") or cm_default_formats
    formats = long_form_multiple_formats(formats)
    for fmt in formats:
        if fmt["extension"] == ext:
            if (not explicit_default) or fmt.get("format_name"):
                return fmt.get("format_name")

    if (not explicit_default) or ext in [".md", ".markdown", ".Rmd"]:
        return None

    return get_format_implementation(ext).format_name
</t>
<t tx="ekr.20241206101345.221">def identical_format_path(fmt1, fmt2):
    """Do the two (long representation) of formats target the same file?"""
    for key in ["extension", "prefix", "suffix"]:
        if fmt1.get(key) != fmt2.get(key):
            return False
    return True
</t>
<t tx="ekr.20241206101345.222">def update_jupytext_formats_metadata(metadata, new_format):
    """Update the jupytext_format metadata in the Jupyter notebook"""
    new_format = long_form_one_format(new_format)
    formats = long_form_multiple_formats(
        metadata.get("jupytext", {}).get("formats", "")
    )
    if not formats:
        return

    for fmt in formats:
        if identical_format_path(fmt, new_format):
            fmt["format_name"] = new_format.get("format_name")
            break

    metadata.setdefault("jupytext", {})["formats"] = short_form_multiple_formats(
        formats
    )
</t>
<t tx="ekr.20241206101345.223">def rearrange_jupytext_metadata(metadata):
    """Convert the jupytext_formats metadata entry to jupytext/formats, etc. See #91"""

    # Backward compatibility with nbrmd
    for key in ["nbrmd_formats", "nbrmd_format_version"]:
        if key in metadata:
            metadata[key.replace("nbrmd", "jupytext")] = metadata.pop(key)

    jupytext_metadata = metadata.pop("jupytext", {})

    if "jupytext_formats" in metadata:
        jupytext_metadata["formats"] = metadata.pop("jupytext_formats")
    if "jupytext_format_version" in metadata:
        jupytext_metadata["text_representation"] = {
            "format_version": metadata.pop("jupytext_format_version")
        }
    if "main_language" in metadata:
        jupytext_metadata["main_language"] = metadata.pop("main_language")
    for entry in ["encoding", "executable"]:
        if entry in metadata:
            jupytext_metadata[entry] = metadata.pop(entry)

    filters = jupytext_metadata.pop("metadata_filter", {})
    if "notebook" in filters:
        jupytext_metadata["notebook_metadata_filter"] = filters["notebook"]
    if "cells" in filters:
        jupytext_metadata["cell_metadata_filter"] = filters["cells"]

    for filter_level in ["notebook_metadata_filter", "cell_metadata_filter"]:
        if filter_level in jupytext_metadata:
            jupytext_metadata[filter_level] = metadata_filter_as_string(
                jupytext_metadata[filter_level]
            )

    if (
        jupytext_metadata.get("text_representation", {})
        .get("jupytext_version", "")
        .startswith("0.")
    ):
        formats = jupytext_metadata.get("formats")
        if formats:
            jupytext_metadata["formats"] = ",".join(
                ["." + fmt if fmt.rfind(".") &gt; 0 else fmt for fmt in formats.split(",")]
            )

    # auto to actual extension
    formats = jupytext_metadata.get("formats")
    if formats:
        jupytext_metadata["formats"] = short_form_multiple_formats(
            long_form_multiple_formats(formats, metadata)
        )

    if jupytext_metadata:
        metadata["jupytext"] = jupytext_metadata
</t>
<t tx="ekr.20241206101345.224">def long_form_one_format(
    jupytext_format, metadata=None, update=None, auto_ext_requires_language_info=True
):
    """Parse 'sfx.py:percent' into {'suffix':'sfx', 'extension':'py', 'format_name':'percent'}"""
    if isinstance(jupytext_format, dict):
        if update:
            jupytext_format.update(update)
        return validate_one_format(jupytext_format)

    if not jupytext_format:
        return {}

    common_name_to_ext = {
        "notebook": "ipynb",
        "rmarkdown": "Rmd",
        "quarto": "qmd",
        "markdown": "md",
        "script": "auto",
        "c++": "cpp",
        "myst": "md:myst",
        "pandoc": "md:pandoc",
    }
    if jupytext_format.lower() in common_name_to_ext:
        jupytext_format = common_name_to_ext[jupytext_format.lower()]

    fmt = {}

    if jupytext_format.rfind("/") &gt; 0:
        fmt["prefix"], jupytext_format = jupytext_format.rsplit("/", 1)

    if jupytext_format.rfind(":") &gt;= 0:
        ext, fmt["format_name"] = jupytext_format.rsplit(":", 1)
        if fmt["format_name"] == "bare":
            warnings.warn(
                "The `bare` format has been renamed to `nomarker` - (see https://github.com/mwouts/jupytext/issues/397)",
                DeprecationWarning,
            )
            fmt["format_name"] = "nomarker"
    elif (
        not jupytext_format
        or "." in jupytext_format
        or ("." + jupytext_format) in NOTEBOOK_EXTENSIONS + [".auto"]
    ):
        ext = jupytext_format
    elif jupytext_format in _VALID_FORMAT_NAMES:
        fmt["format_name"] = jupytext_format
        ext = ""
    else:
        raise JupytextFormatError(
            "'{}' is not a notebook extension (one of {}), nor a notebook format (one of {})".format(
                jupytext_format,
                ", ".join(NOTEBOOK_EXTENSIONS),
                ", ".join(_VALID_FORMAT_NAMES),
            )
        )

    if ext.rfind(".") &gt; 0:
        fmt["suffix"], ext = os.path.splitext(ext)

    if not ext.startswith("."):
        ext = "." + ext

    if ext == ".auto":
        ext = auto_ext_from_metadata(metadata) if metadata is not None else ".auto"
        if not ext:
            if auto_ext_requires_language_info:
                raise JupytextFormatError(
                    "No language information in this notebook. Please replace 'auto' with "
                    "an actual script extension."
                )
            ext = ".auto"

    fmt["extension"] = ext
    if update:
        fmt.update(update)
    return validate_one_format(fmt)
</t>
<t tx="ekr.20241206101345.225">def long_form_multiple_formats(
    jupytext_formats, metadata=None, auto_ext_requires_language_info=True
):
    """Convert a concise encoding of jupytext.formats to a list of formats, encoded as dictionaries"""
    if not jupytext_formats:
        return []

    if not isinstance(jupytext_formats, list):
        jupytext_formats = [fmt for fmt in jupytext_formats.split(",") if fmt]

    jupytext_formats = [
        long_form_one_format(
            fmt,
            metadata,
            auto_ext_requires_language_info=auto_ext_requires_language_info,
        )
        for fmt in jupytext_formats
    ]

    if not auto_ext_requires_language_info:
        jupytext_formats = [
            fmt for fmt in jupytext_formats if fmt["extension"] != ".auto"
        ]

    return jupytext_formats
</t>
<t tx="ekr.20241206101345.226">def short_form_one_format(jupytext_format):
    """Represent one jupytext format as a string"""
    if not isinstance(jupytext_format, dict):
        return jupytext_format
    fmt = jupytext_format["extension"]
    if "suffix" in jupytext_format:
        fmt = jupytext_format["suffix"] + fmt
    elif fmt.startswith("."):
        fmt = fmt[1:]

    if "prefix" in jupytext_format:
        fmt = jupytext_format["prefix"] + "/" + fmt

    if jupytext_format.get("format_name"):
        if jupytext_format["extension"] not in [
            ".md",
            ".markdown",
            ".Rmd",
        ] or jupytext_format["format_name"] in ["pandoc", MYST_FORMAT_NAME]:
            fmt = fmt + ":" + jupytext_format["format_name"]

    return fmt
</t>
<t tx="ekr.20241206101345.227">def short_form_multiple_formats(jupytext_formats):
    """Convert jupytext formats, represented as a list of dictionaries, to a comma separated list"""
    if not isinstance(jupytext_formats, list):
        return jupytext_formats

    jupytext_formats = [short_form_one_format(fmt) for fmt in jupytext_formats]
    return ",".join(jupytext_formats)
</t>
<t tx="ekr.20241206101345.228">_VALID_FORMAT_INFO = ["extension", "format_name", "suffix", "prefix"]
_BINARY_FORMAT_OPTIONS = [
    "comment_magics",
    "hide_notebook_metadata",
    "root_level_metadata_as_raw_cell",
    "split_at_heading",
    "rst2md",
    "cell_metadata_json",
    "use_runtools",
    "doxygen_equation_markers",
]
_VALID_FORMAT_OPTIONS = _BINARY_FORMAT_OPTIONS + [
    "notebook_metadata_filter",
    "cell_metadata_filter",
    "cell_markers",
    "custom_cell_magics",
]
_VALID_FORMAT_NAMES = {fmt.format_name for fmt in JUPYTEXT_FORMATS}


def validate_one_format(jupytext_format):
    """Validate extension and options for the given format"""
    if not isinstance(jupytext_format, dict):
        raise JupytextFormatError("Jupytext format should be a dictionary")

    if (
        "format_name" in jupytext_format
        and jupytext_format["format_name"] not in _VALID_FORMAT_NAMES
    ):
        raise JupytextFormatError(
            "{} is not a valid format name. Please choose one of {}".format(
                jupytext_format.get("format_name"), ", ".join(_VALID_FORMAT_NAMES)
            )
        )

    for key in jupytext_format:
        if key not in _VALID_FORMAT_INFO + _VALID_FORMAT_OPTIONS:
            raise JupytextFormatError(
                "Unknown format option '{}' - should be one of '{}'".format(
                    key, "', '".join(_VALID_FORMAT_OPTIONS)
                )
            )
        value = jupytext_format[key]
        if key in _BINARY_FORMAT_OPTIONS:
            if not isinstance(value, bool):
                raise JupytextFormatError(
                    "Format option '{}' should be a bool, not '{}'".format(
                        key, str(value)
                    )
                )

    if "extension" not in jupytext_format:
        raise JupytextFormatError("Missing format extension")
    ext = jupytext_format["extension"]
    if ext not in NOTEBOOK_EXTENSIONS + [".auto"]:
        raise JupytextFormatError(
            "Extension '{}' is not a notebook extension. Please use one of '{}'.".format(
                ext, "', '".join(NOTEBOOK_EXTENSIONS + [".auto"])
            )
        )

    return jupytext_format
</t>
<t tx="ekr.20241206101345.229">def auto_ext_from_metadata(metadata):
    """Script extension from notebook metadata"""
    auto_ext = metadata.get("language_info", {}).get("file_extension")

    # Sage notebooks have ".py" as the associated extension in "language_info",
    # so we change it to ".sage" in that case, see #727
    if auto_ext == ".py" and metadata.get("kernelspec", {}).get("language") == "sage":
        auto_ext = ".sage"

    if auto_ext is None:
        language = metadata.get("kernelspec", {}).get("language") or metadata.get(
            "jupytext", {}
        ).get("main_language")
        if language:
            for ext in _SCRIPT_EXTENSIONS:
                if same_language(language, _SCRIPT_EXTENSIONS[ext]["language"]):
                    auto_ext = ext
                    break

    if auto_ext == ".r":
        return ".R"

    if auto_ext == ".fs":
        return ".fsx"

    if auto_ext == ".resource":
        return ".robot"

    return auto_ext
</t>
<t tx="ekr.20241206101345.23"></t>
<t tx="ekr.20241206101345.230">def check_auto_ext(fmt, metadata, option):
    """Replace the auto extension with the actual file extension, and raise a ValueError if it cannot be determined"""
    if fmt["extension"] != ".auto":
        return fmt

    auto_ext = auto_ext_from_metadata(metadata)
    if auto_ext:
        fmt = fmt.copy()
        fmt["extension"] = auto_ext
        return fmt

    raise ValueError(
        "The notebook does not have a 'language_info' metadata. "
        "Please replace 'auto' with the actual language extension in the {} option (currently {}).".format(
            option, short_form_one_format(fmt)
        )
    )
</t>
<t tx="ekr.20241206101345.231">def formats_with_support_for_cell_metadata():
    for fmt in JUPYTEXT_FORMATS:
        if fmt.format_name == "myst" and not is_myst_available():
            continue
        if fmt.format_name == "pandoc" and not is_pandoc_available():
            continue
        if fmt.format_name not in ["sphinx", "nomarker", "spin", "quarto"]:
            yield f"{fmt.extension[1:]}:{fmt.format_name}"
</t>
<t tx="ekr.20241206101345.232">"""Parse header of text notebooks
"""

import re

import nbformat
import yaml
from nbformat.v4.nbbase import new_raw_cell
from yaml.representer import SafeRepresenter

from .languages import (
    _SCRIPT_EXTENSIONS,
    comment_lines,
    default_language_from_metadata_and_ext,
)
from .metadata_filter import _DEFAULT_NOTEBOOK_METADATA, filter_metadata
from .pep8 import pep8_lines_between_cells
from .version import __version__

SafeRepresenter.add_representer(nbformat.NotebookNode, SafeRepresenter.represent_dict)

_HEADER_RE = re.compile(r"^---\s*$")
_BLANK_RE = re.compile(r"^\s*$")
_JUPYTER_RE = re.compile(r"^jupyter\s*:\s*$")
_LEFTSPACE_RE = re.compile(r"^\s")
_UTF8_HEADER = " -*- coding: utf-8 -*-"

# Change this to False in tests
INSERT_AND_CHECK_VERSION_NUMBER = True


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.233">def insert_or_test_version_number():
    """Should the format name and version number be inserted in text
    representations (not in tests!)"""
    return INSERT_AND_CHECK_VERSION_NUMBER
</t>
<t tx="ekr.20241206101345.234">def uncomment_line(line, prefix, suffix=""):
    """Remove prefix (and space) from line"""
    if prefix:
        if line.startswith(prefix + " "):
            line = line[len(prefix) + 1 :]
        elif line.startswith(prefix):
            line = line[len(prefix) :]
    if suffix:
        if line.endswith(suffix + " "):
            line = line[: -(1 + len(suffix))]
        elif line.endswith(suffix):
            line = line[: -len(suffix)]
    return line
</t>
<t tx="ekr.20241206101345.235">def encoding_and_executable(notebook, metadata, ext):
    """Return encoding and executable lines for a notebook, if applicable"""
    lines = []
    comment = _SCRIPT_EXTENSIONS.get(ext, {}).get("comment")
    jupytext_metadata = metadata.get("jupytext", {})

    if comment is not None and "executable" in jupytext_metadata:
        lines.append("#!" + jupytext_metadata.pop("executable"))

    if comment is not None:
        if "encoding" in jupytext_metadata:
            lines.append(jupytext_metadata.pop("encoding"))
        elif default_language_from_metadata_and_ext(metadata, ext) != "python":
            for cell in notebook.cells:
                try:
                    cell.source.encode("ascii")
                except (UnicodeEncodeError, UnicodeDecodeError):
                    lines.append(comment + _UTF8_HEADER)
                    break

    return lines
</t>
<t tx="ekr.20241206101345.236">def insert_jupytext_info_and_filter_metadata(
    metadata, fmt, text_format, unsupported_keys
):
    """Update the notebook metadata to include Jupytext information, and filter
    the notebook metadata according to the default or user filter"""
    if insert_or_test_version_number():
        metadata.setdefault("jupytext", {})["text_representation"] = {
            "extension": fmt["extension"],
            "format_name": text_format.format_name,
            "format_version": text_format.current_version_number,
            "jupytext_version": __version__,
        }

    if "jupytext" in metadata and not metadata["jupytext"]:
        del metadata["jupytext"]

    notebook_metadata_filter = fmt.get("notebook_metadata_filter")
    return filter_metadata(
        metadata,
        notebook_metadata_filter,
        _DEFAULT_NOTEBOOK_METADATA,
        unsupported_keys=unsupported_keys,
    )
</t>
<t tx="ekr.20241206101345.237">def metadata_and_cell_to_header(
    notebook, metadata, text_format, fmt, unsupported_keys=None
):
    """
    Return the text header corresponding to a notebook, and remove the
    first cell of the notebook if it contained the header
    """

    header = []

    lines_to_next_cell = None
    root_level_metadata = {}
    root_level_metadata_as_raw_cell = fmt.get("root_level_metadata_as_raw_cell", True)

    if not root_level_metadata_as_raw_cell:
        root_level_metadata = metadata.get("jupytext", {}).pop(
            "root_level_metadata", {}
        )
    elif notebook.cells:
        cell = notebook.cells[0]
        if cell.cell_type == "raw":
            lines = cell.source.strip("\n\t ").splitlines()
            if (
                len(lines) &gt;= 2
                and _HEADER_RE.match(lines[0])
                and _HEADER_RE.match(lines[-1])
            ):
                header = lines[1:-1]
                lines_to_next_cell = cell.metadata.get("lines_to_next_cell")
                notebook.cells = notebook.cells[1:]

    metadata = insert_jupytext_info_and_filter_metadata(
        metadata, fmt, text_format, unsupported_keys
    )

    if metadata:
        root_level_metadata["jupyter"] = metadata

    if root_level_metadata:
        header.extend(
            yaml.safe_dump(root_level_metadata, default_flow_style=False).splitlines()
        )

    if header:
        header = ["---"] + header + ["---"]

        if (
            fmt.get("hide_notebook_metadata", False)
            and text_format.format_name == "markdown"
        ):
            header = ["&lt;!--", ""] + header + ["", "--&gt;"]

    return (
        comment_lines(header, text_format.header_prefix, text_format.header_suffix),
        lines_to_next_cell,
    )
</t>
<t tx="ekr.20241206101345.238">def recursive_update(target, update):
    """Update recursively a (nested) dictionary with the content of another.
    Inspired from https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth
    """
    for key in update:
        value = update[key]
        if value is None:
            del target[key]
        elif isinstance(value, dict):
            target[key] = recursive_update(target.get(key, {}), value)
        else:
            target[key] = value
    return target
</t>
<t tx="ekr.20241206101345.239">def header_to_metadata_and_cell(
    lines, header_prefix, header_suffix, ext=None, root_level_metadata_as_raw_cell=True
):
    """
    Return the metadata, a boolean to indicate if a jupyter section was found,
    the first cell of notebook if some metadata is found outside
    the jupyter section, and next loc in text
    """

    header = []
    jupyter = []
    in_jupyter = False
    in_html_div = False

    start = 0
    started = False
    ended = False
    metadata = {}
    i = -1

    comment = "#" if header_prefix == "#'" else header_prefix

    encoding_re = re.compile(
        rf"^[ \t\f]*{re.escape(comment)}.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)"
    )

    for i, line in enumerate(lines):
        if i == 0 and line.startswith("#!"):
            metadata.setdefault("jupytext", {})["executable"] = line[2:]
            start = i + 1
            continue
        if i == 0 or (i == 1 and not encoding_re.match(lines[0])):
            encoding = encoding_re.match(line)
            if encoding:
                if encoding.group(1) != "utf-8":
                    raise ValueError("Encodings other than utf-8 are not supported")
                metadata.setdefault("jupytext", {})["encoding"] = line
                start = i + 1
                continue
        if not line.startswith(header_prefix):
            break
        if not comment:
            if line.strip().startswith("&lt;!--"):
                in_html_div = True
                continue

        if in_html_div:
            if ended:
                if "--&gt;" in line:
                    break
            if not started and not line.strip():
                continue

        line = uncomment_line(line, header_prefix, header_suffix)
        if _HEADER_RE.match(line):
            if not started:
                started = True
                continue
            ended = True
            if in_html_div:
                continue
            break

        # Stop if there is something else than a YAML header
        if not started and line.strip():
            break

        if _JUPYTER_RE.match(line):
            in_jupyter = True
        elif line and not _LEFTSPACE_RE.match(line):
            in_jupyter = False

        if in_jupyter:
            jupyter.append(line)
        else:
            header.append(line)

    if ended:
        if jupyter:
            extra_metadata = metadata
            metadata = yaml.safe_load("\n".join(jupyter))["jupyter"]
            recursive_update(metadata, extra_metadata)

        lines_to_next_cell = 1
        if len(lines) &gt; i + 1:
            line = uncomment_line(lines[i + 1], header_prefix)
            if not _BLANK_RE.match(line):
                lines_to_next_cell = 0
            else:
                i = i + 1
        else:
            lines_to_next_cell = 0

        if header:
            if root_level_metadata_as_raw_cell:
                cell = new_raw_cell(
                    source="\n".join(["---"] + header + ["---"]),
                    metadata={}
                    if lines_to_next_cell
                    == pep8_lines_between_cells(["---"], lines[i + 1 :], ext)
                    else {"lines_to_next_cell": lines_to_next_cell},
                )
            else:
                cell = None
                root_level_metadata = yaml.safe_load("\n".join(header))
                metadata.setdefault("jupytext", {})[
                    "root_level_metadata"
                ] = root_level_metadata
        else:
            cell = None

        return metadata, jupyter, cell, i + 1

    return metadata, False, None, start
</t>
<t tx="ekr.20241206101345.24">"""Read and write Jupyter notebooks as text files"""

from .formats import NOTEBOOK_EXTENSIONS, get_format_implementation, guess_format
from .jupytext import read, reads, write, writes
from .reraise import reraise
from .version import __version__

try:
    from .contentsmanager import build_jupytext_contents_manager_class
except ImportError as err:
    build_jupytext_contents_manager_class = reraise(err)

try:
    from .contentsmanager import TextFileContentsManager
except ImportError as err:
    TextFileContentsManager = reraise(err)

__all__ = [
    "read",
    "write",
    "writes",
    "reads",
    "NOTEBOOK_EXTENSIONS",
    "guess_format",
    "get_format_implementation",
    "TextFileContentsManager",
    "build_jupytext_contents_manager_class",
    "__version__",
]
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.25">""" Main for Jupytext

Call with (e.g.)::

    python -m jupytext my_notebook.ipynb --to Rmd
"""

import sys

from .cli import jupytext

if __name__ == "__main__":
    sys.exit(jupytext())
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.255">"""Find kernel specifications for a given language"""

import os
import sys

from .languages import same_language
from .reraise import reraise

try:
    # I prefer not to take a dependency on jupyter_client
    from jupyter_client.kernelspec import find_kernel_specs, get_kernel_spec
except ImportError as err:
    find_kernel_specs = reraise(err)
    get_kernel_spec = reraise(err)


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.256">def set_kernelspec_from_language(notebook):
    """Set the kernel specification based on the 'main_language' metadata"""
    language = notebook.metadata.get("jupytext", {}).get("main_language")
    if "kernelspec" not in notebook.metadata and language:
        try:
            kernelspec = kernelspec_from_language(language)
        except ValueError:
            return
        notebook.metadata["kernelspec"] = kernelspec
        notebook.metadata.get("jupytext", {}).pop("main_language")
</t>
<t tx="ekr.20241206101345.257">def kernelspec_from_language(language):
    """Return the python kernel that matches the current env, or the first kernel that matches the given language"""
    if language == "python":
        # Return the kernel that matches the current Python executable
        for name in find_kernel_specs():
            kernel_specs = get_kernel_spec(name)
            cmd = kernel_specs.argv[0]
            if (
                kernel_specs.language == "python"
                and os.path.isfile(cmd)
                and os.path.samefile(cmd, sys.executable)
            ):
                return {
                    "name": name,
                    "language": language,
                    "display_name": kernel_specs.display_name,
                }
        raise ValueError(
            "No kernel found that matches the current python executable {}\n".format(
                sys.executable
            )
            + "Install one with 'python -m ipykernel install --name kernel_name [--user]'"
        )

    for name in find_kernel_specs():
        kernel_specs = get_kernel_spec(name)
        if same_language(kernel_specs.language, language):
            return {
                "name": name,
                "language": language,
                "display_name": kernel_specs.display_name,
            }

    raise ValueError(f"No kernel found for the language {language}")
</t>
<t tx="ekr.20241206101345.258">"""Determine notebook or cell language"""
import re

# Jupyter magic commands that are also languages
_JUPYTER_LANGUAGES = [
    "R",
    "bash",
    "sh",
    "python",
    "python2",
    "python3",
    "coconut",
    "javascript",
    "js",
    "perl",
    "html",
    "latex",
    "markdown",
    "pypy",
    "ruby",
    "script",
    "svg",
    "matlab",
    "octave",
    "idl",
    "robotframework",
    "sas",
    "spark",
    "sql",
    "cython",
    "haskell",
    "tcl",
    "gnuplot",
    "wolfram language",
]

# Supported file extensions (and languages)
# Please add more languages here (and add a few tests) - see contributing.md
_SCRIPT_EXTENSIONS = {
    ".py": {"language": "python", "comment": "#"},
    ".coco": {"language": "coconut", "comment": "#"},
    ".R": {"language": "R", "comment": "#"},
    ".r": {"language": "R", "comment": "#"},
    ".jl": {"language": "julia", "comment": "#"},
    ".cpp": {"language": "c++", "comment": "//"},
    ".ss": {"language": "scheme", "comment": ";;"},
    ".clj": {"language": "clojure", "comment": ";;"},
    ".scm": {"language": "scheme", "comment": ";;"},
    ".sh": {"language": "bash", "comment": "#"},
    ".ps1": {"language": "powershell", "comment": "#"},
    ".q": {"language": "q", "comment": "/"},
    ".m": {"language": "matlab", "comment": "%"},
    # Unfortunately, Wolfram Mathematica also uses the .m extension which
    # conflicts with Matlab. To work around this problem we arbitrarily use a
    # made-up .wolfram extension.
    ".wolfram": {
        "language": "wolfram language",
        "comment": "(*",
        "comment_suffix": "*)",
    },
    ".pro": {"language": "idl", "comment": ";"},
    ".js": {"language": "javascript", "comment": "//"},
    ".ts": {"language": "typescript", "comment": "//"},
    ".scala": {"language": "scala", "comment": "//"},
    ".rs": {"language": "rust", "comment": "//"},
    ".robot": {"language": "robotframework", "comment": "#"},
    ".resource": {"language": "robotframework", "comment": "#"},
    ".cs": {"language": "csharp", "comment": "//"},
    ".fsx": {"language": "fsharp", "comment": "//"},
    ".fs": {"language": "fsharp", "comment": "//"},
    ".sos": {"language": "sos", "comment": "#"},
    ".java": {"language": "java", "comment": "//"},
    ".groovy": {"language": "groovy", "comment": "//"},
    ".sage": {"language": "sage", "comment": "#"},
    ".ml": {
        "language": "ocaml",
        "comment": "(*",
        "comment_suffix": "*)",
    },  # OCaml only has block comments
    ".hs": {"language": "haskell", "comment": "--"},
    ".tcl": {"language": "tcl", "comment": "#"},
    ".mac": {
        "language": "maxima",
        "comment": "/*",
        "comment_suffix": "*/",
    },  # Maxima only has block comments
    ".gp": {"language": "gnuplot", "comment": "#"},
    ".do": {"language": "stata", "comment": "//"},
    ".sas": {
        "language": "sas",
        "comment": "/*",
        "comment_suffix": "*/",
    },
    ".xsh": {"language": "xonsh", "comment": "#"},
    ".lua": {"language": "lua", "comment": "--"},
    ".go": {"language": "go", "comment": "//"},
}

_COMMENT_CHARS = [
    _SCRIPT_EXTENSIONS[ext]["comment"]
    for ext in _SCRIPT_EXTENSIONS
    if _SCRIPT_EXTENSIONS[ext]["comment"] != "#"
]

_COMMENT = {
    _SCRIPT_EXTENSIONS[ext]["language"]: _SCRIPT_EXTENSIONS[ext]["comment"]
    for ext in _SCRIPT_EXTENSIONS
}
_JUPYTER_LANGUAGES = (
    set(_JUPYTER_LANGUAGES).union(_COMMENT.keys()).union(["c#", "f#", "cs", "fs"])
)
_JUPYTER_LANGUAGES_LOWER_AND_UPPER = _JUPYTER_LANGUAGES.union(
    {str.upper(lang) for lang in _JUPYTER_LANGUAGES}
)
_GO_DOUBLE_PERCENT_COMMAND = re.compile(r"^(%%\s*|%%\s+-.*)$")


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.259">def default_language_from_metadata_and_ext(metadata, ext, pop_main_language=False):
    """Return the default language given the notebook metadata, and a file extension"""
    default_from_ext = _SCRIPT_EXTENSIONS.get(ext, {}).get("language")

    main_language = metadata.get("jupytext", {}).get("main_language")
    default_language = (
        metadata.get("kernelspec", {}).get("language") or default_from_ext
    )
    language = main_language or default_language

    if (
        main_language is not None
        and main_language == default_language
        and pop_main_language
    ):
        metadata["jupytext"].pop("main_language")

    if language is None or language in ["R", "sas"]:
        return language

    if language.startswith("C++"):
        return "c++"

    return language.lower().replace("#", "sharp")
</t>
<t tx="ekr.20241206101345.26">"""
Convert between text notebook metadata and jupyter cell metadata.

Standard cell metadata are documented here:
See also https://ipython.org/ipython-doc/3/notebook/nbformat.html#cell-metadata
"""

import ast
import re
from json import dumps, loads

try:
    from json import JSONDecodeError
except ImportError:
    JSONDecodeError = ValueError

from .languages import _JUPYTER_LANGUAGES

# Map R Markdown's "echo", "results" and "include" to "hide_input" and "hide_output", that are understood by the
# `runtools` extension for Jupyter notebook, and by nbconvert (use the `hide_input_output.tpl` template).
# See http://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/runtools/readme.html
_RMARKDOWN_TO_RUNTOOLS_OPTION_MAP = [
    (("include", "FALSE"), [("hide_input", True), ("hide_output", True)]),
    (("echo", "FALSE"), [("hide_input", True)]),
    (("results", "'hide'"), [("hide_output", True)]),
    (("results", '"hide"'), [("hide_output", True)]),
]
# Alternatively, Jupytext can also map the Jupyter Book options to R Markdown
_RMARKDOWN_TO_JUPYTER_BOOK_MAP = [
    (("include", "FALSE"), "remove_cell"),
    (("echo", "FALSE"), "remove_input"),
    (("results", "'hide'"), "remove_output"),
    (("results", '"hide"'), "remove_output"),
]

_JUPYTEXT_CELL_METADATA = [
    # Pre-jupytext metadata
    "skipline",
    "noskipline",
    # Jupytext metadata
    "cell_marker",
    "lines_to_next_cell",
    "lines_to_end_of_cell_marker",
]
_IGNORE_CELL_METADATA = ",".join(
    f"-{name}"
    for name in [
        # Frequent cell metadata that should not enter the text representation
        # (these metadata are preserved in the paired Jupyter notebook).
        "autoscroll",
        "collapsed",
        "scrolled",
        "trusted",
        "execution",
        "ExecuteTime",
    ]
    + _JUPYTEXT_CELL_METADATA
)

# In R Markdown we might have options without a value
_IS_IDENTIFIER = re.compile(r"^[a-zA-Z_\.]+[a-zA-Z0-9_\.]*$")
_IS_VALID_METADATA_KEY = re.compile(r"^[a-zA-Z0-9_\.-]+$")


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.260">def usual_language_name(language):
    """Return the usual language name (one that may be found in _SCRIPT_EXTENSIONS above)"""
    language = language.lower()
    if language == "r":
        return "R"
    if language.startswith("c++"):
        return "c++"
    if language == "octave":
        return "matlab"
    if language in ["cs", "c#"]:
        return "csharp"
    if language in ["fs", "f#"]:
        return "fsharp"
    if language == "sas":
        return "SAS"
    return language
</t>
<t tx="ekr.20241206101345.261">def same_language(kernel_language, language):
    """Are those the same language?"""
    return usual_language_name(kernel_language) == usual_language_name(language)
</t>
<t tx="ekr.20241206101345.262">def set_main_and_cell_language(metadata, cells, ext, custom_cell_magics):
    """Set main language for the given collection of cells, and
    use magics for cells that use other languages"""
    main_language = default_language_from_metadata_and_ext(metadata, ext)

    if main_language is None:
        languages = {"python": 0.5}
        for cell in cells:
            if "language" in cell["metadata"]:
                language = usual_language_name(cell["metadata"]["language"])
                languages[language] = languages.get(language, 0.0) + 1

        main_language = max(languages, key=languages.get)

    # save main language when no kernel is set
    if "language" not in metadata.get("kernelspec", {}) and cells:
        metadata.setdefault("jupytext", {})["main_language"] = main_language

    # Remove 'language' meta data and add a magic if not main language
    for cell in cells:
        if "language" in cell["metadata"]:
            language = cell["metadata"]["language"]
            if language == main_language:
                cell["metadata"].pop("language")
                continue

            if usual_language_name(language) == main_language:
                continue

            if language in _JUPYTER_LANGUAGES or language in custom_cell_magics:
                cell["metadata"].pop("language")
                magic = "%%" if main_language != "csharp" else "#!"
                if "magic_args" in cell["metadata"]:
                    magic_args = cell["metadata"].pop("magic_args")
                    cell["source"] = (
                        f"{magic}{language} {magic_args}\n" + cell["source"]
                    )
                else:
                    cell["source"] = f"{magic}{language}\n" + cell["source"]
</t>
<t tx="ekr.20241206101345.263">def cell_language(source, default_language, custom_cell_magics):
    """Return cell language and language options, if any"""
    if source:
        line = source[0]
        if default_language == "go" and _GO_DOUBLE_PERCENT_COMMAND.match(line):
            return None, None
        if default_language == "csharp":
            if line.startswith("#!"):
                lang = line[2:].strip()
                if lang in _JUPYTER_LANGUAGES:
                    source.pop(0)
                    return lang, ""
        elif line.startswith("%%"):
            magic = line[2:]
            if " " in magic:
                lang, magic_args = magic.split(" ", 1)
            else:
                lang = magic
                magic_args = ""

            if lang in _JUPYTER_LANGUAGES or lang in custom_cell_magics:
                source.pop(0)
                return lang, magic_args

    return None, None
</t>
<t tx="ekr.20241206101345.264">def comment_lines(lines, prefix, suffix=""):
    """Return commented lines"""
    if not prefix:
        return lines
    if not suffix:
        return [prefix + " " + line if line else prefix for line in lines]
    return [
        prefix + " " + line + " " + suffix if line else prefix + " " + suffix
        for line in lines
    ]
</t>
<t tx="ekr.20241206101345.265">"""Escape Jupyter magics when converting to other formats"""

import re

from .languages import _COMMENT, _SCRIPT_EXTENSIONS, usual_language_name
from .stringparser import StringParser


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.266">def get_comment(ext):
    return re.escape(_SCRIPT_EXTENSIONS[ext]["comment"])
</t>
<t tx="ekr.20241206101345.267"># A magic expression is a line or cell or metakernel magic (#94, #61) escaped zero, or multiple times
_MAGIC_RE = {
    _SCRIPT_EXTENSIONS[ext]["language"]: re.compile(
        r"^\s*({0} |{0})*(%|%%|%%%)[a-zA-Z]".format(get_comment(ext))
    )
    for ext in _SCRIPT_EXTENSIONS
}
_MAGIC_FORCE_ESC_RE = {
    _SCRIPT_EXTENSIONS[ext]["language"]: re.compile(
        r"^\s*({0} |{0})*(%|%%|%%%)[a-zA-Z](.*){0}\s*escape".format(get_comment(ext))
    )
    for ext in _SCRIPT_EXTENSIONS
}
_MAGIC_NOT_ESC_RE = {
    _SCRIPT_EXTENSIONS[ext]["language"]: re.compile(
        r"^\s*({0} |{0})*(%|%%|%%%)[a-zA-Z](.*){0}\s*noescape".format(get_comment(ext))
    )
    for ext in _SCRIPT_EXTENSIONS
}
_LINE_CONTINUATION_RE = re.compile(r".*\\\s*$")

# Rust magics start with single ':' #351
_MAGIC_RE["rust"] = re.compile(r"^(// |//)*:[a-zA-Z]")
_MAGIC_FORCE_ESC_RE["rust"] = re.compile(r"^(// |//)*:[a-zA-Z](.*)//\s*escape")
_MAGIC_FORCE_ESC_RE["rust"] = re.compile(r"^(// |//)*:[a-zA-Z](.*)//\s*noescape")

# C# magics start with '#!'
_MAGIC_RE["csharp"] = re.compile(r"^(// |//)*#![a-zA-Z]")
_MAGIC_FORCE_ESC_RE["csharp"] = re.compile(r"^(// |//)*#![a-zA-Z](.*)//\s*escape")
_MAGIC_FORCE_ESC_RE["csharp"] = re.compile(r"^(// |//)*#![a-zA-Z](.*)//\s*noescape")

# Go magics might start with % or ! or !*
# (in addition, Go NB might use %% or %% -, see "_GO_DOUBLE_PERCENT_COMMAND")
_MAGIC_RE["go"] = re.compile(r"^(// |//)*(!|!\*|%|%%|%%%)[a-zA-Z]")

# Commands starting with a question or exclamation mark have to be escaped
_PYTHON_HELP_OR_BASH_CMD = re.compile(r"^\s*(# |#)*\s*(\?|!)\s*[A-Za-z\.\~\$\\\/\{\}]")

# A bash command not followed by an equal sign or a parenthesis is a magic command
_PYTHON_MAGIC_CMD = re.compile(
    r"^(# |#)*({})($|\s$|\s[^=,])".format(
        "|".join(
            # posix
            ["cat", "cd", "cp", "mv", "rm", "rmdir", "mkdir"]
            +  # noqa: W504
            # windows
            ["copy", "ddir", "echo", "ls", "ldir", "mkdir", "ren", "rmdir"]
        )
    )
)
# Python help commands end with ?
_IPYTHON_MAGIC_HELP = re.compile(r"^\s*(# )*[^\s]*\?\s*$")

_PYTHON_MAGIC_ASSIGN = re.compile(
    r"^(# |#)*\s*([a-zA-Z_][a-zA-Z_$0-9]*)\s*=\s*(%|%%|%%%|!)[a-zA-Z](.*)"
)

_SCRIPT_LANGUAGES = [_SCRIPT_EXTENSIONS[ext]["language"] for ext in _SCRIPT_EXTENSIONS]


def is_magic(line, language, global_escape_flag=True, explicitly_code=False):
    """Is the current line a (possibly escaped) Jupyter magic, and should it be commented?"""
    language = usual_language_name(language)
    if language in ["octave", "matlab", "sas"] or language not in _SCRIPT_LANGUAGES:
        return False
    if _MAGIC_FORCE_ESC_RE[language].match(line):
        return True
    if not global_escape_flag or _MAGIC_NOT_ESC_RE[language].match(line):
        return False
    if _MAGIC_RE[language].match(line):
        return True
    if language != "python":
        return False
    if _PYTHON_HELP_OR_BASH_CMD.match(line):
        return True
    if _PYTHON_MAGIC_ASSIGN.match(line):
        return True
    if explicitly_code and _IPYTHON_MAGIC_HELP.match(line):
        return True
    return _PYTHON_MAGIC_CMD.match(line)
</t>
<t tx="ekr.20241206101345.268">def need_explicit_marker(
    source, language="python", global_escape_flag=True, explicitly_code=True
):
    """Does this code needs an explicit cell marker?"""
    if language != "python" or not global_escape_flag or not explicitly_code:
        return False

    parser = StringParser(language)
    for line in source:
        if not parser.is_quoted() and is_magic(
            line, language, global_escape_flag, explicitly_code
        ):
            if not is_magic(line, language, global_escape_flag, False):
                return True
        parser.read_line(line)
    return False
</t>
<t tx="ekr.20241206101345.269">def comment_magic(
    source, language="python", global_escape_flag=True, explicitly_code=True
):
    """Escape Jupyter magics with '# '"""
    parser = StringParser(language)
    next_is_magic = False
    for pos, line in enumerate(source):
        if not parser.is_quoted() and (
            next_is_magic
            or is_magic(line, language, global_escape_flag, explicitly_code)
        ):
            if next_is_magic:
                # this is the continuation line of a magic command on the previous line,
                # so we don't want to indent the comment
                unindented = line
                indent = ""
            else:
                unindented = line.lstrip()
                indent = line[: len(line) - len(unindented)]
            source[pos] = indent + _COMMENT[language] + " " + unindented
            next_is_magic = language == "python" and _LINE_CONTINUATION_RE.match(line)
        parser.read_line(line)
    return source
</t>
<t tx="ekr.20241206101345.27">class RLogicalValueError(Exception):
    """Incorrect value for R boolean"""
</t>
<t tx="ekr.20241206101345.270">def unesc(line, language):
    """Uncomment once a commented line"""
    comment = _COMMENT[language]
    unindented = line.lstrip()
    indent = line[: len(line) - len(unindented)]
    if unindented.startswith(comment + " "):
        return indent + unindented[len(comment) + 1 :]
    if unindented.startswith(comment):
        return indent + unindented[len(comment) :]
    return line
</t>
<t tx="ekr.20241206101345.271">def uncomment_magic(
    source, language="python", global_escape_flag=True, explicitly_code=True
):
    """Unescape Jupyter magics"""
    parser = StringParser(language)
    next_is_magic = False
    for pos, line in enumerate(source):
        if not parser.is_quoted() and (
            next_is_magic
            or is_magic(line, language, global_escape_flag, explicitly_code)
        ):
            source[pos] = unesc(line, language)
            next_is_magic = language == "python" and _LINE_CONTINUATION_RE.match(line)
        parser.read_line(line)
    return source
</t>
<t tx="ekr.20241206101345.272">_ESCAPED_CODE_START = {
    ".Rmd": re.compile(r"^(# |#)*```{.*}"),
    ".md": re.compile(r"^(# |#)*```"),
    ".markdown": re.compile(r"^(# |#)*```"),
}
_ESCAPED_CODE_START.update(
    {
        ext: re.compile(r"^({0} |{0})*({0}|{0} )\+".format(get_comment(ext)))
        for ext in _SCRIPT_EXTENSIONS
    }
)


def is_escaped_code_start(line, ext):
    """Is the current line a possibly commented code start marker?"""
    return _ESCAPED_CODE_START[ext].match(line)
</t>
<t tx="ekr.20241206101345.273">def escape_code_start(source, ext, language="python"):
    """Escape code start with '# '"""
    parser = StringParser(language)
    for pos, line in enumerate(source):
        if not parser.is_quoted() and is_escaped_code_start(line, ext):
            source[pos] = (
                _SCRIPT_EXTENSIONS.get(ext, {}).get("comment", "#") + " " + line
            )
        parser.read_line(line)
    return source
</t>
<t tx="ekr.20241206101345.274">def unescape_code_start(source, ext, language="python"):
    """Unescape code start"""
    parser = StringParser(language)
    for pos, line in enumerate(source):
        if not parser.is_quoted() and is_escaped_code_start(line, ext):
            unescaped = unesc(line, language)
            # don't remove comment char if we break the code start...
            if is_escaped_code_start(unescaped, ext):
                source[pos] = unescaped
        parser.read_line(line)
    return source
</t>
<t tx="ekr.20241206101345.275">"""Notebook and cell metadata filtering"""

from copy import copy

from .cell_metadata import _JUPYTEXT_CELL_METADATA, is_valid_metadata_key

_DEFAULT_NOTEBOOK_METADATA = ",".join(
    [
        # Preserve Jupytext section
        "jupytext",
        # Preserve kernel specs
        "kernelspec",
        # Kernel_info found in Nteract notebooks
        "kernel_info",
        # Used in MyST notebooks
        "orphan",
        "tocdepth",
    ]
)


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.276">def metadata_filter_as_dict(metadata_config):
    """Return the metadata filter represented as either None (no filter),
    or a dictionary with at most two keys: 'additional' and 'excluded',
    which contain either a list of metadata names, or the string 'all'"""

    if metadata_config is None:
        return {}

    if metadata_config is True:
        return {"additional": "all"}

    if metadata_config is False:
        return {"excluded": "all"}

    if isinstance(metadata_config, dict):
        assert set(metadata_config) &lt;= {"additional", "excluded"}
        return metadata_config

    metadata_keys = metadata_config.split(",")

    metadata_config = {}

    for key in metadata_keys:
        key = key.strip()
        if key.startswith("-"):
            metadata_config.setdefault("excluded", []).append(key[1:].strip())
        elif key.startswith("+"):
            metadata_config.setdefault("additional", []).append(key[1:].strip())
        else:
            metadata_config.setdefault("additional", []).append(key)

    for section in metadata_config:
        if "all" in metadata_config[section]:
            metadata_config[section] = "all"
        else:
            metadata_config[section] = [k for k in metadata_config[section] if k]

    return metadata_config
</t>
<t tx="ekr.20241206101345.277">def metadata_filter_as_string(metadata_filter):
    """Convert a filter, represented as a dictionary with 'additional' and 'excluded' entries, to a string"""
    if not isinstance(metadata_filter, dict):
        return metadata_filter

    additional = metadata_filter.get("additional", [])
    if additional == "all":
        entries = ["all"]
    else:
        entries = [key for key in additional if key not in _JUPYTEXT_CELL_METADATA]

    excluded = metadata_filter.get("excluded", [])
    if excluded == "all":
        entries.append("-all")
    else:
        entries.extend(["-" + e for e in excluded])

    return ",".join(entries)
</t>
<t tx="ekr.20241206101345.278">def update_metadata_filters(metadata, jupyter_md, cell_metadata):
    """Update or set the notebook and cell metadata filters"""

    if not jupyter_md:
        # Set a metadata filter equal to the current metadata in script
        metadata.setdefault("jupytext", {})["notebook_metadata_filter"] = "-all"
        metadata["jupytext"].setdefault(
            "cell_metadata_filter",
            metadata_filter_as_string({"additional": cell_metadata, "excluded": "all"}),
        )
    elif "cell_metadata_filter" in metadata.get("jupytext", {}):
        # Update the existing metadata filter
        metadata_filter = metadata_filter_as_dict(
            metadata.get("jupytext", {})["cell_metadata_filter"]
        )
        if isinstance(metadata_filter.get("excluded"), list):
            metadata_filter["excluded"] = [
                key for key in metadata_filter["excluded"] if key not in cell_metadata
            ]
        metadata_filter.setdefault("additional", [])
        if isinstance(metadata_filter.get("additional"), list):
            for key in cell_metadata:
                if key not in metadata_filter["additional"]:
                    metadata_filter["additional"].append(key)
        metadata.setdefault("jupytext", {})[
            "cell_metadata_filter"
        ] = metadata_filter_as_string(metadata_filter)
    else:
        # Update the notebook metadata filter to include existing entries 376
        nb_md_filter = (
            metadata.get("jupytext", {}).get("notebook_metadata_filter", "").split(",")
        )
        nb_md_filter = [key for key in nb_md_filter if key]
        if "all" in nb_md_filter or "-all" in nb_md_filter:
            return
        for key in metadata:
            if (
                key in _DEFAULT_NOTEBOOK_METADATA.split(",")
                or key in nb_md_filter
                or ("-" + key) in nb_md_filter
            ):
                continue
            nb_md_filter.append(key)
        if nb_md_filter:
            metadata.setdefault("jupytext", {})["notebook_metadata_filter"] = ",".join(
                nb_md_filter
            )
</t>
<t tx="ekr.20241206101345.279">def filter_metadata(metadata, user_filter, default_filter="", unsupported_keys=None):
    """Filter the cell or notebook metadata, according to the user preference"""
    default_filter = metadata_filter_as_dict(default_filter) or {}
    user_filter = metadata_filter_as_dict(user_filter) or {}

    default_exclude = default_filter.get("excluded", [])
    default_include = default_filter.get("additional", [])

    assert not (default_exclude == "all" and default_include == "all")
    if isinstance(default_include, list) and default_include and default_exclude == []:
        default_exclude = "all"

    user_exclude = user_filter.get("excluded", [])
    user_include = user_filter.get("additional", [])

    # notebook default filter = include only few metadata
    if default_exclude == "all":
        if user_include == "all":
            return subset_metadata(
                metadata, exclude=user_exclude, unsupported_keys=unsupported_keys
            )
        if user_exclude == "all":
            return subset_metadata(
                metadata, keep_only=user_include, unsupported_keys=unsupported_keys
            )
        return subset_metadata(
            metadata,
            keep_only=set(user_include).union(default_include),
            exclude=user_exclude,
            unsupported_keys=unsupported_keys,
        )

    # cell default filter = all metadata but removed ones
    if user_include == "all":
        return subset_metadata(
            metadata, exclude=user_exclude, unsupported_keys=unsupported_keys
        )
    if user_exclude == "all":
        return subset_metadata(
            metadata, keep_only=user_include, unsupported_keys=unsupported_keys
        )
    # Do not serialize empty tags
    if "tags" in metadata and not metadata["tags"]:
        metadata = metadata.copy()
        metadata.pop("tags")
    return subset_metadata(
        metadata,
        exclude=set(user_exclude).union(set(default_exclude).difference(user_include)),
        unsupported_keys=unsupported_keys,
    )
</t>
<t tx="ekr.20241206101345.28">class RMarkdownOptionParsingError(Exception):
    """Error when parsing Rmd cell options"""
</t>
<t tx="ekr.20241206101345.280">def second_level(keys):
    """Return a dictionary with the nested keys, e.g. returns {'I':['a', 'b']} when keys=['I.a', 'I.b']"""
    sub_keys = {}
    for key in keys:
        if "." in key:
            left, right = key.split(".", 1)
            sub_keys.setdefault(left, []).append(right)

    return sub_keys
</t>
<t tx="ekr.20241206101345.281">def suppress_unsupported_keys(metadata, unsupported_keys=None):
    # assert isinstance(unsupported_keys, set)
    if isinstance(unsupported_keys, set):
        for key in metadata:
            if not is_valid_metadata_key(key):
                unsupported_keys.add(key)
    return {key: value for key, value in metadata.items() if is_valid_metadata_key(key)}
</t>
<t tx="ekr.20241206101345.282">def subset_metadata(metadata, keep_only=None, exclude=None, unsupported_keys=None):
    """Filter the metadata"""
    metadata = suppress_unsupported_keys(metadata, unsupported_keys=unsupported_keys)
    if keep_only is not None:
        filtered_metadata = {key: metadata[key] for key in metadata if key in keep_only}
        sub_keep_only = second_level(keep_only)
        for key in sub_keep_only:
            if key in metadata:
                filtered_metadata[key] = subset_metadata(
                    metadata[key],
                    keep_only=sub_keep_only[key],
                    unsupported_keys=unsupported_keys,
                )
    else:
        filtered_metadata = copy(metadata)

    if exclude is not None:
        for key in exclude:
            if key in filtered_metadata:
                filtered_metadata.pop(key)
        sub_exclude = second_level(exclude)
        for key in sub_exclude:
            if key in filtered_metadata:
                filtered_metadata[key] = subset_metadata(
                    filtered_metadata[key],
                    exclude=sub_exclude[key],
                    unsupported_keys=unsupported_keys,
                )

    return filtered_metadata
</t>
<t tx="ekr.20241206101345.283">def restore_filtered_metadata(
    filtered_metadata, unfiltered_metadata, user_filter, default_filter
):
    """Update the filtered metadata with the part of the unfiltered one that matches the filter"""
    filtered_unfiltered_metadata = filter_metadata(
        unfiltered_metadata, user_filter, default_filter
    )

    metadata = copy(filtered_metadata)
    for key in unfiltered_metadata:
        if key not in filtered_unfiltered_metadata:
            # We don't want to restore the line_to_next_cell metadata from the ipynb file, see #761
            if key not in _JUPYTEXT_CELL_METADATA:
                metadata[key] = unfiltered_metadata[key]

    return metadata
</t>
<t tx="ekr.20241206101345.284">"""
This module contains round-trip conversion between
myst formatted text documents and notebooks.
"""
import json
import re
import warnings
from textwrap import dedent

import nbformat as nbf
import yaml

from .cell_to_text import three_backticks_or_more

try:
    from markdown_it import MarkdownIt
    from mdit_py_plugins.front_matter import front_matter_plugin
    from mdit_py_plugins.myst_blocks import myst_block_plugin
    from mdit_py_plugins.myst_role import myst_role_plugin
except ImportError:
    MarkdownIt = None

MYST_FORMAT_NAME = "myst"
CODE_DIRECTIVE = "{code-cell}"
RAW_DIRECTIVE = "{raw-cell}"


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.285">def is_myst_available():
    """Whether the markdown-it-py package is available."""
    return MarkdownIt is not None
</t>
<t tx="ekr.20241206101345.286">def raise_if_myst_is_not_available():
    if not is_myst_available():
        raise ImportError(
            "The MyST Markdown format requires python &gt;= 3.6 and markdown-it-py~=1.0"
        )
</t>
<t tx="ekr.20241206101345.287">def myst_version():
    """The version of myst."""
    return 0.13
</t>
<t tx="ekr.20241206101345.288">def myst_extensions(no_md=False):
    """The allowed extensions for the myst format."""
    if no_md:
        return [".myst", ".mystnb", ".mnb"]
    return [".md", ".myst", ".mystnb", ".mnb"]
</t>
<t tx="ekr.20241206101345.289">def get_parser():
    """Return the markdown-it parser to use."""
    parser = (
        MarkdownIt("commonmark")
        .enable("table")
        .use(front_matter_plugin)
        .use(myst_block_plugin)
        .use(myst_role_plugin)
        # we only need to parse block level components (for efficiency)
        .disable("inline", True)
    )
    return parser
</t>
<t tx="ekr.20241206101345.29">def _py_logical_values(rbool):
    if rbool in ["TRUE", "T"]:
        return True
    if rbool in ["FALSE", "F"]:
        return False
    raise RLogicalValueError
</t>
<t tx="ekr.20241206101345.290">def matches_mystnb(
    text,
    ext=None,
    requires_meta=True,
    code_directive=CODE_DIRECTIVE,
    raw_directive=RAW_DIRECTIVE,
):
    """Attempt to distinguish a file as myst, only given its extension and content.

    :param ext: the extension of the file
    :param requires_meta: requires the file to contain top matter metadata
    :param code_directive: the name of the directive to search for containing code cells
    :param raw_directive: the name of the directive to search for containing raw cells
    """
    # is the extension uniquely associated with myst (i.e. not just .md)
    if ext and "." + ("." + ext).rsplit(".", 1)[1] in myst_extensions(no_md=True):
        return True

    # might the text contain metadata front matter
    if requires_meta and not text.startswith("---"):
        return False

    try:
        tokens = get_parser().parse(text + "\n")
    except (TypeError, ValueError) as err:
        warnings.warn(f"myst-parser failed unexpectedly: {err}")  # pragma: no cover
        return False

    # Is the format information available in the jupytext text representation?
    if tokens and tokens[0].type == "front_matter":
        try:
            metadata = yaml.safe_load(tokens[0].content)
        except (yaml.parser.ParserError, yaml.scanner.ScannerError):
            pass
        else:
            try:
                if (
                    metadata.get("jupytext", {})
                    .get("text_representation", {})
                    .get("format_name", "")
                    == MYST_FORMAT_NAME
                ):
                    return True
            except AttributeError:
                pass

    # is there at least on fenced code block with a code/raw directive language
    for token in tokens:
        if token.type == "fence" and (
            token.info.startswith(code_directive)
            or token.info.startswith(raw_directive)
        ):
            return True

    return False
</t>
<t tx="ekr.20241206101345.291">class CompactDumper(yaml.SafeDumper):
    """This YAML dumper creates a more compact style for lists"""
</t>
<t tx="ekr.20241206101345.292">def represent_list(self, data):
    """Compact lists"""
    flow_style = not any(isinstance(i, dict) for i in data)
    return self.represent_sequence("tag:yaml.org,2002:seq", data, flow_style=flow_style)
</t>
<t tx="ekr.20241206101345.293">def represent_dict(self, data):
    """Compact dicts"""
    return self.represent_mapping("tag:yaml.org,2002:map", data, flow_style=False)
</t>
<t tx="ekr.20241206101345.294">CompactDumper.add_representer(list, represent_list)
CompactDumper.add_representer(dict, represent_dict)


def dump_yaml_blocks(data, compact=True):
    """Where possible, we try to use a more compact metadata style.

    For blocks with no nested dicts, the block is denoted by starting colons::

        :other: true
        :tags: [hide-output, show-input]

    For blocks with nesting the block is enlosed by ``---``::

        ---
        other:
            more: true
        tags: [hide-output, show-input]
        ---
    """
    string = yaml.dump(data, Dumper=CompactDumper)
    lines = string.splitlines()
    if compact and all(line and line[0].isalpha() for line in lines):
        return "\n".join([f":{line}" for line in lines]) + "\n\n"
    return f"---\n{string}---\n"
</t>
<t tx="ekr.20241206101345.295">def from_nbnode(value):
    """Recursively convert NotebookNode to dict."""
    if isinstance(value, nbf.NotebookNode):
        return {k: from_nbnode(v) for k, v in value.items()}
    return value
</t>
<t tx="ekr.20241206101345.296">class MystMetadataParsingError(Exception):
    """Error when parsing metadata from myst formatted text"""
</t>
<t tx="ekr.20241206101345.297">def strip_blank_lines(text):
    """Remove initial blank lines"""
    text = text.rstrip()
    while text and text.startswith("\n"):
        text = text[1:]
    return text
</t>
<t tx="ekr.20241206101345.298">def read_fenced_cell(token, cell_index, cell_type):
    """Parse (and validate) the full directive text."""
    content = token.content
    error_msg = "{} cell {} at line {} could not be read: ".format(
        cell_type, cell_index, token.map[0] + 1
    )

    body_lines, options = parse_directive_options(content, error_msg)

    # remove first line of body if blank
    # this is to allow space between the options and the content
    if body_lines and not body_lines[0].strip():
        body_lines = body_lines[1:]

    return options, body_lines
</t>
<t tx="ekr.20241206101345.299">def parse_directive_options(content, error_msg):
    """Parse (and validate) the directive option section."""
    options = {}
    if content.startswith("---"):
        content = "\n".join(content.splitlines()[1:])
        match = re.search(r"^-{3,}", content, re.MULTILINE)
        if match:
            yaml_block = content[: match.start()]
            content = content[match.end() + 1 :]
        else:
            yaml_block = content
            content = ""
        yaml_block = dedent(yaml_block)
        try:
            options = yaml.safe_load(yaml_block) or {}
        except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
            raise MystMetadataParsingError(error_msg + "Invalid YAML; " + str(error))
    elif content.lstrip().startswith(":"):
        content_lines = content.splitlines()  # type: list
        yaml_lines = []
        while content_lines:
            if not content_lines[0].lstrip().startswith(":"):
                break
            yaml_lines.append(content_lines.pop(0).lstrip()[1:])
        yaml_block = "\n".join(yaml_lines)
        content = "\n".join(content_lines)
        try:
            options = yaml.safe_load(yaml_block) or {}
        except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
            raise MystMetadataParsingError(error_msg + "Invalid YAML; " + str(error))

    return content.splitlines(), options
</t>
<t tx="ekr.20241206101345.3">@language rust

// Characters, length 3.
a = 'x'
b = 'é'

// Characters, length 4.
nl = '\n'
null = '\0'
tab = '\t'
ret = '\r'
bs = '\\'

// Characters, length 10 and 8.
char10 = '\u{7fff}'
char8 = '\x7f'

// Non characters.
other 'xx
bad_escaple '\y' 

// Lifetimes.
pub fn &lt;'a&gt;:
</t>
<t tx="ekr.20241206101345.30">def metadata_to_rmd_options(language, metadata, use_runtools=False):
    """Convert language and metadata information to their rmd representation"""
    options = (language or "R").lower()
    if "name" in metadata:
        options += " " + metadata["name"] + ","
        del metadata["name"]
    if use_runtools:
        for rmd_option, jupyter_options in _RMARKDOWN_TO_RUNTOOLS_OPTION_MAP:
            if all(
                [
                    metadata.get(opt_name) == opt_value
                    for opt_name, opt_value in jupyter_options
                ]
            ):
                options += " {}={},".format(
                    rmd_option[0], "FALSE" if rmd_option[1] is False else rmd_option[1]
                )
                for opt_name, _ in jupyter_options:
                    metadata.pop(opt_name)
    else:
        for rmd_option, tag in _RMARKDOWN_TO_JUPYTER_BOOK_MAP:
            if tag in metadata.get("tags", []):
                options += " {}={},".format(
                    rmd_option[0], "FALSE" if rmd_option[1] is False else rmd_option[1]
                )
                metadata["tags"] = [i for i in metadata["tags"] if i != tag]
                if not metadata["tags"]:
                    metadata.pop("tags")
    for opt_name in metadata:
        opt_value = metadata[opt_name]
        opt_name = opt_name.strip()
        if opt_name == "active":
            options += f' {opt_name}="{str(opt_value)}",'
        elif isinstance(opt_value, bool):
            options += " {}={},".format(opt_name, "TRUE" if opt_value else "FALSE")
        elif isinstance(opt_value, list):
            options += " {}={},".format(
                opt_name,
                "c({})".format(", ".join([f'"{str(v)}"' for v in opt_value])),
            )
        elif isinstance(opt_value, str):
            if opt_value.startswith("#R_CODE#"):
                options += f" {opt_name}={opt_value[8:]},"
            elif '"' not in opt_value:
                options += f' {opt_name}="{opt_value}",'
            else:
                options += f" {opt_name}='{opt_value}',"
        else:
            options += f" {opt_name}={str(opt_value)},"
    if not language:
        options = options[2:]
    return options.strip(",").strip()
</t>
<t tx="ekr.20241206101345.300">def read_cell_metadata(token, cell_index):
    """Return cell metadata"""
    metadata = {}
    if token.content:
        try:
            metadata = json.loads(token.content.strip())
        except Exception as err:
            raise MystMetadataParsingError(
                "Markdown cell {} at line {} could not be read: {}".format(
                    cell_index, token.map[0] + 1, err
                )
            )
        if not isinstance(metadata, dict):
            raise MystMetadataParsingError(
                "Markdown cell {} at line {} is not a dict".format(
                    cell_index, token.map[0] + 1
                )
            )

    return metadata
</t>
<t tx="ekr.20241206101345.301">def myst_to_notebook(
    text,
    code_directive=CODE_DIRECTIVE,
    raw_directive=RAW_DIRECTIVE,
    add_source_map=False,
):
    """Convert text written in the myst format to a notebook.

    :param text: the file text
    :param code_directive: the name of the directive to search for containing code cells
    :param raw_directive: the name of the directive to search for containing raw cells
    :param add_source_map: add a `source_map` key to the notebook metadata,
        which is a list of the starting source line number for each cell.

    :raises MystMetadataParsingError if the metadata block is not valid JSON/YAML

    NOTE: we assume here that all of these directives are at the top-level,
    i.e. not nested in other directives.
    """
    raise_if_myst_is_not_available()

    tokens = get_parser().parse(text + "\n")
    lines = text.splitlines()
    md_start_line = 0

    # get the document metadata
    metadata_nb = {}
    if tokens and tokens[0].type == "front_matter":
        metadata = tokens.pop(0)
        md_start_line = metadata.map[1]
        try:
            metadata_nb = yaml.safe_load(metadata.content)
        except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
            raise MystMetadataParsingError(f"Notebook metadata: {error}")

    # create an empty notebook
    nbf_version = nbf.v4
    kwargs = {"metadata": nbf.from_dict(metadata_nb)}
    notebook = nbf_version.new_notebook(**kwargs)
    source_map = []  # this is a list of the starting line number for each cell

    def _flush_markdown(start_line, token, md_metadata):
        """When we find a cell we check if there is preceding text.o"""
        endline = token.map[0] if token else len(lines)
        md_source = strip_blank_lines("\n".join(lines[start_line:endline]))
        meta = nbf.from_dict(md_metadata)
        if md_source:
            source_map.append(start_line)
            notebook.cells.append(
                nbf_version.new_markdown_cell(source=md_source, metadata=meta)
            )

    # iterate through the tokens to identify notebook cells
    nesting_level = 0
    md_metadata = {}

    for token in tokens:
        nesting_level += token.nesting

        if nesting_level != 0:
            # we ignore fenced block that are nested, e.g. as part of lists, etc
            continue

        if token.type == "fence" and token.info.startswith(code_directive):
            _flush_markdown(md_start_line, token, md_metadata)
            options, body_lines = read_fenced_cell(token, len(notebook.cells), "Code")
            meta = nbf.from_dict(options)
            source_map.append(token.map[0] + 1)
            notebook.cells.append(
                nbf_version.new_code_cell(source="\n".join(body_lines), metadata=meta)
            )
            md_metadata = {}
            md_start_line = token.map[1]

        elif token.type == "fence" and token.info.startswith(raw_directive):
            _flush_markdown(md_start_line, token, md_metadata)
            options, body_lines = read_fenced_cell(token, len(notebook.cells), "Raw")
            meta = nbf.from_dict(options)
            source_map.append(token.map[0] + 1)
            notebook.cells.append(
                nbf_version.new_raw_cell(source="\n".join(body_lines), metadata=meta)
            )
            md_metadata = {}
            md_start_line = token.map[1]

        elif token.type == "myst_block_break":
            _flush_markdown(md_start_line, token, md_metadata)
            md_metadata = read_cell_metadata(token, len(notebook.cells))
            md_start_line = token.map[1]

    _flush_markdown(md_start_line, None, md_metadata)

    if add_source_map:
        notebook.metadata["source_map"] = source_map
    return notebook
</t>
<t tx="ekr.20241206101345.302">def notebook_to_myst(
    nb,
    code_directive=CODE_DIRECTIVE,
    raw_directive=RAW_DIRECTIVE,
    default_lexer=None,
):
    """Parse a notebook to a MyST formatted text document.

    :param nb: the notebook to parse
    :param code_directive: the name of the directive to use for code cells
    :param raw_directive: the name of the directive to use for raw cells
    :param default_lexer: a lexer name to use for annotating code cells
        (if ``nb.metadata.language_info.pygments_lexer`` is not available)
    """
    raise_if_myst_is_not_available()
    string = ""

    nb_metadata = from_nbnode(nb.metadata)

    # we add the pygments lexer as a directive argument, for use by syntax highlighters
    pygments_lexer = nb_metadata.get("language_info", {}).get("pygments_lexer", None)
    if pygments_lexer is None:
        pygments_lexer = default_lexer

    if nb_metadata:
        string += dump_yaml_blocks(nb_metadata, compact=False)

    last_cell_md = False
    for i, cell in enumerate(nb.cells):
        if cell.cell_type == "markdown":
            metadata = from_nbnode(cell.metadata)
            if metadata or last_cell_md:
                if metadata:
                    string += f"\n+++ {json.dumps(metadata)}\n"
                else:
                    string += "\n+++\n"
            string += "\n" + cell.source
            if not cell.source.endswith("\n"):
                string += "\n"
            last_cell_md = True

        elif cell.cell_type in ["code", "raw"]:
            cell_delimiter = three_backticks_or_more(cell.source.splitlines())
            string += "\n{}{}".format(
                cell_delimiter,
                code_directive if cell.cell_type == "code" else raw_directive,
            )
            if pygments_lexer and cell.cell_type == "code":
                string += f" {pygments_lexer}"
            string += "\n"
            metadata = from_nbnode(cell.metadata)
            if metadata:
                string += dump_yaml_blocks(metadata)
            elif cell.source.startswith("---") or cell.source.startswith(":"):
                string += "\n"
            string += cell.source
            if not cell.source.endswith("\n"):
                string += "\n"
            string += cell_delimiter + "\n"
            last_cell_md = False

        else:
            raise NotImplementedError(f"cell {i}, type: {cell.cell_type}")

    return string.rstrip() + "\n"
</t>
<t tx="ekr.20241206101345.303">"""List all the paths associated to a given notebook"""

import os

from .config import find_jupytext_configuration_file
from .formats import (
    NOTEBOOK_EXTENSIONS,
    long_form_multiple_formats,
    long_form_one_format,
    short_form_multiple_formats,
    short_form_one_format,
)


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.304">class InconsistentPath(ValueError):
    """An exception raised when the path of a notebook is not consistent with the jupytext.format
    information it contains"""
</t>
<t tx="ekr.20241206101345.305">def split(path, sep):
    if sep not in path:
        return "", path

    return path.rsplit(sep, 1)
</t>
<t tx="ekr.20241206101345.306">def join(left, right, sep):
    if left:
        return left + sep + right
    return right
</t>
<t tx="ekr.20241206101345.307">def separator(path):
    """Return the local path separator (always / in the contents manager)"""
    if os.path.sep == "\\" and "\\" in path:
        return "\\"
    return "/"
</t>
<t tx="ekr.20241206101345.308">def base_path(main_path, fmt, formats=None):
    """Given a path and options for a format (ext, suffix, prefix), return the corresponding base path"""
    fmt = long_form_one_format(fmt)

    base, ext = os.path.splitext(main_path)
    if "extension" not in fmt:
        fmt["extension"] = ext
        if ext not in NOTEBOOK_EXTENSIONS:
            raise InconsistentPath(
                "'{}' is not a notebook. Supported extensions are '{}'.".format(
                    main_path, "', '".join(NOTEBOOK_EXTENSIONS)
                )
            )

    if ext != fmt["extension"]:
        raise InconsistentPath(
            "Notebook path '{}' was expected to have extension '{}'".format(
                main_path, fmt["extension"]
            )
        )

    # Is there a format that matches the main path?
    if formats is None:
        formats = [fmt]

    for f in formats:
        if f["extension"] != fmt["extension"]:
            continue
        if (
            "format_name" in fmt
            and "format_name" in f
            and f["format_name"] != fmt["format_name"]
        ):
            continue
        # extend 'fmt' with the format information (prefix, suffix) from f
        fmt = {key: fmt.get(key, value) for key, value in f.items()}
        break

    suffix = fmt.get("suffix")
    prefix = fmt.get("prefix")

    if suffix:
        if not base.endswith(suffix):
            raise InconsistentPath(
                "Notebook name '{}' was expected to end with suffix '{}'".format(
                    base, suffix
                )
            )
        base = base[: -len(suffix)]

    if not prefix:
        return base

    if "//" in prefix:
        prefix_root, prefix = prefix.rsplit("//", 1)
    else:
        prefix_root = ""
    sep = separator(base)
    notebook_dir, notebook_file_name = split(base, sep)
    prefix_dir, prefix_file_name = split(prefix, "/")

    base_dir = None
    config_file = find_jupytext_configuration_file(notebook_dir)
    if config_file:
        config_file_dir = os.path.dirname(config_file)
        if notebook_dir.startswith(config_file_dir):
            base_dir = config_file_dir
            notebook_dir = notebook_dir[len(config_file_dir) :]

    if prefix_file_name:
        if not notebook_file_name.startswith(prefix_file_name):
            raise InconsistentPath(
                "Notebook name '{}' was expected to start with prefix '{}'".format(
                    notebook_file_name, prefix_file_name
                )
            )
        notebook_file_name = notebook_file_name[len(prefix_file_name) :]

    if prefix_dir:
        parent_notebook_dir = notebook_dir
        parent_prefix_dir = prefix_dir
        actual_folders = list()
        while parent_prefix_dir:
            parent_prefix_dir, expected_folder = split(parent_prefix_dir, "/")
            if expected_folder == "..":
                if not actual_folders:
                    raise InconsistentPath(
                        "Notebook directory '{}' does not match prefix '{}'".format(
                            notebook_dir, prefix_dir
                        )
                    )
                parent_notebook_dir = join(
                    parent_notebook_dir, actual_folders.pop(), sep
                )
            else:
                parent_notebook_dir, actual_folder = split(parent_notebook_dir, sep)
                actual_folders.append(actual_folder)

                if actual_folder != expected_folder:
                    raise InconsistentPath(
                        "Notebook directory '{}' does not match prefix '{}'".format(
                            notebook_dir, prefix_dir
                        )
                    )
        notebook_dir = parent_notebook_dir

    if prefix_root:
        long_prefix_root = sep + prefix_root + sep
        long_notebook_dir = sep + notebook_dir + sep
        if long_prefix_root not in long_notebook_dir:
            raise InconsistentPath(
                "Notebook directory '{}' does not match prefix root '{}'".format(
                    notebook_dir, prefix_root
                )
            )
        left, right = long_notebook_dir.rsplit(long_prefix_root, 1)
        notebook_dir = left + sep + "//" + right

        # We are going to remove the last char, but we need to insert it back in the end...
        if not right:
            sep = notebook_dir[-1]
        notebook_dir = notebook_dir[len(sep) : -len(sep)]

    if base_dir:
        notebook_dir = base_dir + notebook_dir

    if not notebook_dir:
        return notebook_file_name

    return notebook_dir + sep + notebook_file_name
</t>
<t tx="ekr.20241206101345.309">def full_path(base, fmt):
    """Return the full path for the notebook, given the base path"""
    ext = fmt["extension"]
    suffix = fmt.get("suffix")
    prefix = fmt.get("prefix")

    full = base

    if prefix:
        if "//" in prefix:
            prefix_root, prefix = prefix.rsplit("//", 1)
        else:
            prefix_root = ""
        prefix_dir, prefix_file_name = split(prefix, "/")

        # Local path separator (\\ on windows)
        sep = separator(base)
        prefix_dir = prefix_dir.replace("/", sep)

        if (prefix_root != "") != ("//" in base):
            raise InconsistentPath(
                "Notebook base name '{}' is not compatible with fmt={}. Make sure you use prefix roots "
                "in either none, or all of the paired formats".format(
                    base, short_form_one_format(fmt)
                )
            )
        if prefix_root:
            left, right = base.rsplit("//", 1)
            right_dir, notebook_file_name = split(right, sep)
            notebook_dir = left + prefix_root + sep + right_dir
        else:
            notebook_dir, notebook_file_name = split(base, sep)

        if prefix_file_name:
            notebook_file_name = prefix_file_name + notebook_file_name

        if prefix_dir:
            dotdot = ".." + sep
            while prefix_dir.startswith(dotdot):
                prefix_dir = prefix_dir[len(dotdot) :]
                notebook_dir = split(notebook_dir, sep)[0]

            # Do not add a path separator when notebook_dir is '/'
            if notebook_dir and not notebook_dir.endswith(sep):
                notebook_dir = notebook_dir + sep

            notebook_dir = notebook_dir + prefix_dir

        if notebook_dir and not notebook_dir.endswith(sep):
            notebook_dir = notebook_dir + sep

        full = notebook_dir + notebook_file_name

    if suffix:
        full = full + suffix

    return full + ext
</t>
<t tx="ekr.20241206101345.31">def update_metadata_from_rmd_options(name, value, metadata, use_runtools=False):
    """Map the R Markdown cell visibility options to the Jupyter ones"""
    if use_runtools:
        for rmd_option, jupyter_options in _RMARKDOWN_TO_RUNTOOLS_OPTION_MAP:
            if name == rmd_option[0] and value == rmd_option[1]:
                for opt_name, opt_value in jupyter_options:
                    metadata[opt_name] = opt_value
                return True
    else:
        for rmd_option, tag in _RMARKDOWN_TO_JUPYTER_BOOK_MAP:
            if name == rmd_option[0] and value == rmd_option[1]:
                metadata.setdefault("tags", []).append(tag)
                return True
    return False
</t>
<t tx="ekr.20241206101345.310">def find_base_path_and_format(main_path, formats):
    """Return the base path and the format corresponding to the given path"""
    for fmt in formats:
        try:
            return base_path(main_path, fmt), fmt
        except InconsistentPath:
            continue

    raise InconsistentPath(
        "Path '{}' matches none of the export formats. "
        "Please make sure that jupytext.formats covers the current file "
        "(e.g. add '{}' to the export formats)".format(
            main_path, os.path.splitext(main_path)[1][1:]
        )
    )
</t>
<t tx="ekr.20241206101345.311">def paired_paths(main_path, fmt, formats):
    """Return the list of paired notebooks, given main path, and the list of formats"""
    if not formats:
        return [(main_path, {"extension": os.path.splitext(main_path)[1]})]

    formats = long_form_multiple_formats(formats)

    # Is there a format that matches the main path?
    base = base_path(main_path, fmt, formats)
    paths = [full_path(base, f) for f in formats]

    if main_path not in paths:
        raise InconsistentPath(
            "Paired paths '{}' do not include the current notebook path '{}'. "
            "Current format is '{}', and paired formats are '{}'.".format(
                "','".join(paths),
                main_path,
                short_form_one_format(fmt),
                short_form_multiple_formats(formats),
            )
        )

    if len(paths) &gt; len(set(paths)):
        raise InconsistentPath(
            "Duplicate paired paths for this notebook. Please fix jupytext.formats."
        )

    return list(zip(paths, formats))
</t>
<t tx="ekr.20241206101345.312">"""Functions to read or write paired notebooks"""

from collections import namedtuple

import jupytext

from .combine import combine_inputs_with_outputs
from .compare import compare
from .formats import (
    check_file_version,
    long_form_multiple_formats,
    long_form_one_format,
)
from .paired_paths import find_base_path_and_format, full_path, paired_paths

NotebookFile = namedtuple("notebook_file", "path fmt timestamp")


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.313">class PairedFilesDiffer(ValueError):
    """An error when the two representations of a paired notebook differ"""
</t>
<t tx="ekr.20241206101345.314">def write_pair(path, formats, write_one_file):
    """
    Call the function 'write_one_file' on each of the paired path/formats
    """
    formats = long_form_multiple_formats(formats)
    base, _ = find_base_path_and_format(path, formats)

    # Save as ipynb first
    return_value = None
    value = None
    for fmt in formats[::-1]:
        if fmt["extension"] != ".ipynb":
            continue

        alt_path = full_path(base, fmt)
        value = write_one_file(alt_path, fmt)
        if alt_path == path:
            return_value = value

    # And then to the other formats, in reverse order so that
    # the first format is the most recent
    for fmt in formats[::-1]:
        if fmt["extension"] == ".ipynb":
            continue

        alt_path = full_path(base, fmt)
        value = write_one_file(alt_path, fmt)
        if alt_path == path:
            return_value = value

    # Update modified timestamp to match that of the pair #207
    if isinstance(return_value, dict) and "last_modified" in return_value:
        return_value["last_modified"] = value["last_modified"]

    return return_value
</t>
<t tx="ekr.20241206101345.315">def latest_inputs_and_outputs(
    path, fmt, formats, get_timestamp, contents_manager_mode=False
):
    """Given a notebook path, its format and paired formats, and a function that
    returns the timestamp for each (or None if the file does not exist), return
    the most recent notebook for the inputs and outputs, respectively"""

    timestamp_inputs = None
    timestamp_outputs = None
    inputs_path = None
    outputs_path = None
    input_fmt = None
    output_fmt = None

    fmt = long_form_one_format(fmt)
    formats = long_form_multiple_formats(formats)

    for alt_path, alt_fmt in paired_paths(path, fmt, formats):
        # In the contents manager, we don't read another text file if the current notebook is in already in text mode
        if (
            contents_manager_mode
            and alt_fmt["extension"] != ".ipynb"
            and fmt["extension"] != ".ipynb"
        ):
            if any(
                alt_fmt.get(key) != fmt.get(key)
                for key in ["extension", "suffix", "prefix"]
            ):
                continue

        timestamp = get_timestamp(alt_path)
        if timestamp is None:
            continue

        if alt_fmt["extension"] == ".ipynb":
            if timestamp_outputs is None or timestamp &gt; timestamp_outputs:
                timestamp_outputs = timestamp
                outputs_path, output_fmt = alt_path, alt_fmt
        elif timestamp_inputs is None or timestamp &gt; timestamp_inputs:
            timestamp_inputs = timestamp
            inputs_path, input_fmt = alt_path, alt_fmt

    if timestamp_inputs is None or (
        not contents_manager_mode
        and timestamp_outputs
        and timestamp_outputs &gt; timestamp_inputs
    ):
        timestamp_inputs = timestamp_outputs
        inputs_path, input_fmt = outputs_path, output_fmt

    return (
        NotebookFile(inputs_path, input_fmt, timestamp_inputs),
        NotebookFile(outputs_path, output_fmt, timestamp_outputs),
    )
</t>
<t tx="ekr.20241206101345.316">def read_pair(inputs, outputs, read_one_file, must_match=False):
    """Read a notebook given its inputs and outputs path and formats"""
    if not outputs.path or outputs.path == inputs.path:
        return read_one_file(inputs.path, inputs.fmt)

    notebook = read_one_file(inputs.path, inputs.fmt)
    check_file_version(notebook, inputs.path, outputs.path)

    notebook_with_outputs = read_one_file(outputs.path, outputs.fmt)

    if must_match:
        in_text = jupytext.writes(notebook, inputs.fmt)
        out_text = jupytext.writes(notebook_with_outputs, inputs.fmt)
        diff = compare(out_text, in_text, outputs.path, inputs.path, return_diff=True)
        if diff:
            raise PairedFilesDiffer(diff)

    notebook = combine_inputs_with_outputs(
        notebook, notebook_with_outputs, fmt=inputs.fmt
    )

    return notebook
</t>
<t tx="ekr.20241206101345.317">"""Jupyter notebook to Markdown and back, using Pandoc"""

import os
import subprocess
import tempfile

# Copy nbformat reads and writes to avoid them being patched in the contents manager!!
from nbformat import reads as ipynb_reads
from nbformat import writes as ipynb_writes
from packaging.version import parse


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.318">class PandocError(OSError):
    """An error related to Pandoc"""
</t>
<t tx="ekr.20241206101345.319">def pandoc(args, filein=None, fileout=None):
    """Execute pandoc with the given arguments"""
    cmd = ["pandoc"]

    if filein:
        cmd.append(filein)

    if fileout:
        cmd.append("-o")
        cmd.append(fileout)

    cmd.extend(args.split())

    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    out, err = proc.communicate()
    if proc.returncode:
        raise PandocError(
            f"pandoc exited with return code {proc.returncode}\n{str(err)}"
        )
    return out.decode("utf-8")
</t>
<t tx="ekr.20241206101345.32">class ParsingContext:
    """
    Class for determining where to split rmd options
    """
    @others
</t>
<t tx="ekr.20241206101345.320">def is_pandoc_available(min_version="2.7.2", max_version=None):
    """Is Pandoc&gt;=2.7.2 available?"""
    try:
        raise_if_pandoc_is_not_available(
            min_version=min_version, max_version=max_version
        )
        return True
    except PandocError:
        return False
</t>
<t tx="ekr.20241206101345.321">def raise_if_pandoc_is_not_available(min_version="2.7.2", max_version=None):
    """Raise with an informative error message if pandoc is not available"""
    version = pandoc_version()
    if version == "N/A":
        raise PandocError(
            f"The Pandoc Markdown format requires 'pandoc&gt;={min_version}', "
            "but pandoc was not found"
        )

    if parse(version) &lt; parse(min_version):
        raise PandocError(
            f"The Pandoc Markdown format requires 'pandoc&gt;={min_version}', "
            f"but pandoc version {version} was found"
        )

    if max_version and parse(version) &gt; parse(max_version):
        raise PandocError(
            f"The Pandoc Markdown format requires 'pandoc&lt;={max_version}', "
            f"but pandoc version {version} was found"
        )

    return version
</t>
<t tx="ekr.20241206101345.322">def pandoc_version():
    """Pandoc's version number"""
    try:
        return pandoc("--version").splitlines()[0].split()[1]
    except OSError:
        return "N/A"
</t>
<t tx="ekr.20241206101345.323">def md_to_notebook(text):
    """Convert a Markdown text to a Jupyter notebook, using Pandoc"""
    raise_if_pandoc_is_not_available()
    tmp_file = tempfile.NamedTemporaryFile(delete=False)
    tmp_file.write(text.encode("utf-8"))
    tmp_file.close()

    if parse(pandoc_version()) &lt; parse("2.11.2"):
        pandoc_args = "--from markdown --to ipynb -s --atx-headers --wrap=preserve --preserve-tabs"
    else:
        pandoc_args = "--from markdown --to ipynb -s --markdown-headings=atx --wrap=preserve --preserve-tabs"
    pandoc(
        pandoc_args,
        tmp_file.name,
        tmp_file.name,
    )

    with open(tmp_file.name, encoding="utf-8") as opened_file:
        notebook = ipynb_reads(opened_file.read(), as_version=4)
    os.unlink(tmp_file.name)

    return notebook
</t>
<t tx="ekr.20241206101345.324">def notebook_to_md(notebook):
    """Convert a notebook to its Markdown representation, using Pandoc"""
    raise_if_pandoc_is_not_available()
    tmp_file = tempfile.NamedTemporaryFile(delete=False)
    tmp_file.write(ipynb_writes(notebook).encode("utf-8"))
    tmp_file.close()

    if parse(pandoc_version()) &lt; parse("2.11.2"):
        pandoc_args = "--from ipynb --to markdown -s --atx-headers --wrap=preserve --preserve-tabs"
    else:
        pandoc_args = "--from ipynb --to markdown -s --markdown-headings=atx --wrap=preserve --preserve-tabs"
    pandoc(
        pandoc_args,
        tmp_file.name,
        tmp_file.name,
    )

    with open(tmp_file.name, encoding="utf-8") as opened_file:
        text = opened_file.read()

    os.unlink(tmp_file.name)
    return "\n".join(text.splitlines())
</t>
<t tx="ekr.20241206101345.325">"""Determine how many blank lines should be inserted between two cells"""
from .stringparser import StringParser


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.326">def next_instruction_is_function_or_class(lines):
    """Is the first non-empty, non-commented line of the cell either a function or a class?"""
    parser = StringParser("python")
    for i, line in enumerate(lines):
        if parser.is_quoted():
            parser.read_line(line)
            continue
        parser.read_line(line)
        if not line.strip():  # empty line
            if i &gt; 0 and not lines[i - 1].strip():
                return False
            continue
        if line.startswith("def ") or line.startswith("class "):
            return True
        if line.startswith(("#", "@", " ", ")")):
            continue
        return False

    return False
</t>
<t tx="ekr.20241206101345.327">def cell_ends_with_function_or_class(lines):
    """Does the last line of the cell belong to an indented code?"""
    non_quoted_lines = []
    parser = StringParser("python")
    for line in lines:
        if not parser.is_quoted():
            non_quoted_lines.append(line)
        parser.read_line(line)

    # find the first line, starting from the bottom, that is not indented
    lines = non_quoted_lines[::-1]
    for i, line in enumerate(lines):
        if not line.strip():
            # two blank lines? we won't need to insert more blank lines below this cell
            if i &gt; 0 and not lines[i - 1].strip():
                return False
            continue
        if line.startswith(("#", " ", ")")):
            continue
        if line.startswith("def ") or line.startswith("class "):
            return True
        return False

    return False
</t>
<t tx="ekr.20241206101345.328">def cell_ends_with_code(lines):
    """Is the last line of the cell a line with code?"""
    if not lines:
        return False
    if not lines[-1].strip():
        return False
    if lines[-1].startswith("#"):
        return False
    return True
</t>
<t tx="ekr.20241206101345.329">def cell_has_code(lines):
    """Is there any code in this cell?"""
    for i, line in enumerate(lines):
        stripped_line = line.strip()
        if stripped_line.startswith("#"):
            continue

        # Two consecutive blank lines?
        if not stripped_line:
            if i &gt; 0 and not lines[i - 1].strip():
                return False
            continue

        return True

    return False
</t>
<t tx="ekr.20241206101345.33">parenthesis_count = 0
curly_bracket_count = 0
square_bracket_count = 0
in_single_quote = False
in_double_quote = False

def __init__(self, line):
    self.line = line
</t>
<t tx="ekr.20241206101345.330">def pep8_lines_between_cells(prev_lines, next_lines, ext):
    """How many blank lines should be added between the two python paragraphs to make them pep8?"""
    if not next_lines:
        return 1
    if not prev_lines:
        return 0
    if ext != ".py":
        return 1
    if cell_ends_with_function_or_class(prev_lines):
        return 2 if cell_has_code(next_lines) else 1
    if cell_ends_with_code(prev_lines) and next_instruction_is_function_or_class(
        next_lines
    ):
        return 2
    return 1
</t>
<t tx="ekr.20241206101345.331">"""Jupyter notebook to Quarto Markdown and back, using 'quarto convert'"""

import os
import subprocess
import tempfile

# Copy nbformat reads and writes to avoid them being patched in the contents manager!!
from nbformat import reads as ipynb_reads
from nbformat import writes as ipynb_writes
from packaging.version import parse

QUARTO_MIN_VERSION = "0.2.134"


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.332">class QuartoError(OSError):
    """An error related to Quarto"""
</t>
<t tx="ekr.20241206101345.333">def quarto(args, filein=None):
    """Execute quarto with the given arguments"""
    cmd = ["quarto"] + args.split()

    if filein:
        cmd.append(filein)

    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    if proc.returncode:
        raise QuartoError(
            f"{' '.join(cmd)} exited with return code {proc.returncode}\n{err.decode('utf-8')}"
        )

    return out.decode("utf-8")
</t>
<t tx="ekr.20241206101345.334">def is_quarto_available(min_version=QUARTO_MIN_VERSION):
    """Is Quarto available?"""
    try:
        raise_if_quarto_is_not_available(min_version=min_version)
        return True
    except QuartoError:
        return False
</t>
<t tx="ekr.20241206101345.335">def raise_if_quarto_is_not_available(min_version=QUARTO_MIN_VERSION):
    """Raise with an informative error message if quarto is not available"""
    version = quarto_version()
    if version == "N/A":
        raise QuartoError(
            f"The Quarto Markdown format requires 'quarto&gt;={min_version}', "
            "but quarto was not found"
        )

    if parse(version) &lt; parse(min_version):
        raise QuartoError(
            f"The Quarto Markdown format requires 'quarto&gt;={min_version}', "
            f"but quarto version {version} was not found"
        )

    return version
</t>
<t tx="ekr.20241206101345.336">def quarto_version():
    """Quarto's version number"""
    try:
        return quarto("--version").strip()
    except OSError:
        return "N/A"
</t>
<t tx="ekr.20241206101345.337">def qmd_to_notebook(text):
    """Convert a Quarto Markdown notebook to a Jupyter notebook"""
    raise_if_quarto_is_not_available()
    tmp_qmd_file = tempfile.NamedTemporaryFile(delete=False, suffix=".qmd")
    tmp_qmd_file.write(text.encode("utf-8"))
    tmp_qmd_file.close()

    quarto("convert --log-level warning", tmp_qmd_file.name)

    tmp_ipynb_file_name = tmp_qmd_file.name[:-4] + ".ipynb"

    with open(tmp_ipynb_file_name, encoding="utf-8") as ipynb_file:
        notebook = ipynb_reads(ipynb_file.read(), as_version=4)

    os.unlink(tmp_qmd_file.name)
    os.unlink(tmp_ipynb_file_name)

    return notebook
</t>
<t tx="ekr.20241206101345.338">def notebook_to_qmd(notebook):
    """Convert a Jupyter notebook to its Quarto Markdown representation"""
    raise_if_quarto_is_not_available()
    tmp_ipynb_file = tempfile.NamedTemporaryFile(delete=False, suffix=".ipynb")
    tmp_ipynb_file.write(ipynb_writes(notebook).encode("utf-8"))
    tmp_ipynb_file.close()

    quarto("convert --log-level warning", tmp_ipynb_file.name)

    tmp_qmd_file_name = tmp_ipynb_file.name[:-6] + ".qmd"

    with open(tmp_qmd_file_name, encoding="utf-8") as qmd_file:
        text = qmd_file.read()

    os.unlink(tmp_ipynb_file.name)
    os.unlink(tmp_qmd_file_name)

    return "\n".join(text.splitlines())
</t>
<t tx="ekr.20241206101345.339">"""Raise the given error at evaluation time"""


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.34">def in_global_expression(self):
    """Currently inside an expression"""
    return (
        self.parenthesis_count == 0
        and self.curly_bracket_count == 0
        and self.square_bracket_count == 0
        and not self.in_single_quote
        and not self.in_double_quote
    )
</t>
<t tx="ekr.20241206101345.340">def reraise(error):
    """Return a function that raises the given error when evaluated"""

    def local_function(*args, **kwargs):
        raise error

    return local_function
</t>
<t tx="ekr.20241206101345.341">"""A simple file parser that can tell whether the first character of a line
is quoted or not"""
from .languages import _COMMENT


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.342">class StringParser:
    """A simple file parser that can tell whether the first character of a line
    is quoted or not"""
    @others
</t>
<t tx="ekr.20241206101345.343">single = None
triple = None
triple_start = None

def __init__(self, language):
    self.ignore = language is None
    self.python = language != "R"
    self.comment = _COMMENT.get(language)
</t>
<t tx="ekr.20241206101345.344">def is_quoted(self):
    """Is the next line quoted?"""
    if self.ignore:
        return False
    return self.single or self.triple
</t>
<t tx="ekr.20241206101345.345">def read_line(self, line):
    """Read a new line"""
    if self.ignore:
        return

    # Do not search for quotes when the line is commented out (and not quoted)
    if (
        not self.is_quoted()
        and self.comment is not None
        and line.lstrip().startswith(self.comment)
    ):
        return

    self.triple_start = -1

    for i, char in enumerate(line):
        if (
            self.single is None
            and self.triple is None
            and self.comment
            and self.comment.startswith(char)
            and line[i:].startswith(self.comment)
        ):
            break
        if char not in ['"', "'"]:
            continue
        # Is the char escaped?
        if line[i - 1 : i] == "\\":
            continue

        if self.single == char:
            self.single = None
            continue
        if self.single is not None:
            continue

        if not self.python:
            continue

        if line[i - 2 : i + 1] == 3 * char and i &gt;= self.triple_start + 3:
            # End of a triple quote
            if self.triple == char:
                self.triple = None
                self.triple_start = i
                continue

            # Are we looking for a different triple quote?
            if self.triple is not None:
                continue

            # Triple quote starting
            self.triple = char
            self.triple_start = i
            continue

        # Inside a multiline quote
        if self.triple is not None:
            continue

        self.single = char

    # Line ended
    if self.python:
        self.single = None
</t>
<t tx="ekr.20241206101345.346">"""Jupytext's version number"""

__version__ = "1.16.4"
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.347"></t>
<t tx="ekr.20241206101345.348">@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.349">""" Main for Jupytext_config

Call with (e.g.)::

    python -m jupytext_config list-default-viewer
"""

import sys

from .jupytext_config import main

if __name__ == "__main__":
    sys.exit(main())
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.35">def count_special_chars(self, char, prev_char):
    """Update parenthesis counters"""
    if char == "(":
        self.parenthesis_count += 1
    elif char == ")":
        self.parenthesis_count -= 1
        if self.parenthesis_count &lt; 0:
            raise RMarkdownOptionParsingError(
                'Option line "{}" has too many '
                "closing parentheses".format(self.line)
            )
    elif char == "{":
        self.curly_bracket_count += 1
    elif char == "}":
        self.curly_bracket_count -= 1
        if self.curly_bracket_count &lt; 0:
            raise RMarkdownOptionParsingError(
                'Option line "{}" has too many '
                "closing curly brackets".format(self.line)
            )
    elif char == "[":
        self.square_bracket_count += 1
    elif char == "]":
        self.square_bracket_count -= 1
        if self.square_bracket_count &lt; 0:
            raise RMarkdownOptionParsingError(
                'Option line "{}" has too many '
                "closing square brackets".format(self.line)
            )
    elif char == "'" and prev_char != "\\" and not self.in_double_quote:
        self.in_single_quote = not self.in_single_quote
    elif char == '"' and prev_char != "\\" and not self.in_single_quote:
        self.in_double_quote = not self.in_double_quote
</t>
<t tx="ekr.20241206101345.350">"""
the code for
jupytext-config set-default-viewer
and related subcommands
"""

import sys
from argparse import ArgumentParser
from pathlib import Path

import jupyter_core.paths as jupyter_core_paths

from .labconfig import LabConfig


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.351">class SubCommand:
    """
    a subcommand for jupytext-config
    """
    @others
</t>
<t tx="ekr.20241206101345.352">def __init__(self, name, help):
    self.name = name
    self.help = help
</t>
<t tx="ekr.20241206101345.353">def main(self, args):
    """
    return 0 if all goes well
    """
    raise NotImplementedError()  # pragma: no cover
</t>
<t tx="ekr.20241206101345.354">class ListDefaultViewer(SubCommand):
    @others
</t>
<t tx="ekr.20241206101345.355">def __init__(self):
    super().__init__("list-default-viewer", "Display current settings in labconfig")
</t>
<t tx="ekr.20241206101345.356">def main(self, args):
    LabConfig(settings_file=args.settings_file).read().list_default_viewer()
    return 0
</t>
<t tx="ekr.20241206101345.357">def fill_parser(self, subparser):
    pass
</t>
<t tx="ekr.20241206101345.358">class SetDefaultViewer(SubCommand):
    @others
</t>
<t tx="ekr.20241206101345.359">def __init__(self):
    super().__init__("set-default-viewer", "Set default viewers for JupyterLab")
</t>
<t tx="ekr.20241206101345.36">def parse_rmd_options(line):
    """
    Given a R markdown option line, returns a list of pairs name,value
    :param line:
    :return:
    """
    parsing_context = ParsingContext(line)

    result = []
    prev_char = ""

    name = ""
    value = ""

    for char in "," + line + ",":
        if parsing_context.in_global_expression():
            if char == ",":
                if name != "" or value != "":
                    if result and name == "":
                        raise RMarkdownOptionParsingError(
                            'Option line "{}" has no name for '
                            "option value {}".format(line, value)
                        )
                    result.append((name.strip(), value.strip()))
                    name = ""
                    value = ""
            elif char == "=":
                if name == "":
                    name = value
                    value = ""
                else:
                    value += char
            else:
                parsing_context.count_special_chars(char, prev_char)
                value += char
        else:
            parsing_context.count_special_chars(char, prev_char)
            value += char
        prev_char = char

    if not parsing_context.in_global_expression():
        raise RMarkdownOptionParsingError(
            f'Option line "{line}" is not properly terminated'
        )

    return result
</t>
<t tx="ekr.20241206101345.360">def main(self, args):
    LabConfig(settings_file=args.settings_file).read().set_default_viewers(
        args.doctype
    ).write()
    return 0
</t>
<t tx="ekr.20241206101345.361">def fill_parser(self, subparser):
    subparser.add_argument(
        "doctype",
        nargs="*",
        help=f"the document types to be associated with the notebook editor; "
        f"defaults to {' '.join(LabConfig.DOCTYPES)}",
    )
</t>
<t tx="ekr.20241206101345.362">class UnsetDefaultViewer(SubCommand):
    @others
</t>
<t tx="ekr.20241206101345.363">def __init__(self):
    super().__init__("unset-default-viewer", "Unset default viewers for JupyterLab")
</t>
<t tx="ekr.20241206101345.364">def main(self, args):
    LabConfig(settings_file=args.settings_file).read().unset_default_viewers(
        args.doctype
    ).write()
    return 0
</t>
<t tx="ekr.20241206101345.365">def fill_parser(self, subparser):
    subparser.add_argument(
        "doctype",
        nargs="*",
        help=f"the document types for which the default viewer will be unset; "
        f"defaults to {' '.join(LabConfig.DOCTYPES)}",
    )
</t>
<t tx="ekr.20241206101345.366"># create the subcommands
SUBCOMMANDS = [
    ListDefaultViewer(),
    SetDefaultViewer(),
    UnsetDefaultViewer(),
]


def main():
    parser = ArgumentParser()
    parser.add_argument(
        "--settings-file",
        default=Path(jupyter_core_paths.jupyter_config_dir())
        / "labconfig"
        / "default_setting_overrides.json",
    )
    subparsers = parser.add_subparsers(required=True)
    for subcommand in SUBCOMMANDS:
        subparser = subparsers.add_parser(subcommand.name, help=subcommand.help)
        subparser.set_defaults(subcommand=subcommand)
        subcommand.fill_parser(subparser)
    args = parser.parse_args(sys.argv[1:] or ["--help"])
    return args.subcommand.main(args)
</t>
<t tx="ekr.20241206101345.367">"""
helper to inspect / initialize jupyterlab labconfig settings
that are required to open jupytext notebooks in jupyterlab by default
when these settings are not present, a double click on a jupytext
notebook will cause jupyterlab to open it in an editor, i.e. as a text file
"""

import json
import logging
from pathlib import Path


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.368">class LabConfig:
    @others
</t>
<t tx="ekr.20241206101345.369">DOCTYPES = [
    "python",
    "markdown",
    "myst",
    "r-markdown",
    "quarto",
    "julia",
    "r",
]

def __init__(self, *, settings_file, logger=None):
    self.settings_file = Path(settings_file)
    self.logger = logger or logging.getLogger(__name__)
    self.config = {}
</t>
<t tx="ekr.20241206101345.37">def rmd_options_to_metadata(options, use_runtools=False):
    """Parse rmd options and return a metadata dictionary"""
    options = re.split(r"\s|,", options, maxsplit=1)
    # Special case Wolfram Language, which sadly has a space in the language
    # name.
    if options[0:2] == ["wolfram", "language"]:
        options[0:2] = ["wolfram language"]
    if len(options) == 1:
        language = options[0]
        chunk_options = []
    else:
        language, others = options
        language = language.rstrip(" ,")
        others = others.lstrip(" ,")
        chunk_options = parse_rmd_options(others)

    language = "R" if language == "r" else language
    metadata = {}
    for i, opt in enumerate(chunk_options):
        name, value = opt
        if i == 0 and name == "":
            metadata["name"] = value
            continue
        if update_metadata_from_rmd_options(
            name, value, metadata, use_runtools=use_runtools
        ):
            continue
        try:
            metadata[name] = _py_logical_values(value)
            continue
        except RLogicalValueError:
            metadata[name] = value

    for name in metadata:
        try_eval_metadata(metadata, name)

    if "eval" in metadata and not is_active(".Rmd", metadata):
        del metadata["eval"]

    return metadata.get("language") or language, metadata
</t>
<t tx="ekr.20241206101345.370">def read(self):
    """
    read the labconfig settings file
    """
    if self.settings_file.exists():
        with self.settings_file.open() as fid:
            self.config = json.load(fid)
    else:
        self.logger.info(f"Could not read from {self.settings_file} (not found)")

    return self
</t>
<t tx="ekr.20241206101345.371">def get_viewers(self):
    return self.config.setdefault(
        "@jupyterlab/docmanager-extension:plugin", {}
    ).setdefault("defaultViewers", {})
</t>
<t tx="ekr.20241206101345.372">def list_default_viewer(self):
    """
    list the current labconfig settings
    """
    self.logger.debug(
        f"Current @jupyterlab/docmanager-extension:plugin in {self.settings_file}"
    )
    for key, value in self.get_viewers().items():
        print(f"{key}: {value}")
</t>
<t tx="ekr.20241206101345.373">def set_default_viewers(self, doctypes=None):
    if not doctypes:
        doctypes = self.DOCTYPES
    for doctype in doctypes:
        self.set_default_viewer(doctype)
    return self
</t>
<t tx="ekr.20241206101345.374">def set_default_viewer(self, doctype):
    viewers = self.get_viewers()
    if doctype not in viewers:
        viewers[doctype] = "Jupytext Notebook"
</t>
<t tx="ekr.20241206101345.375">def unset_default_viewers(self, doctypes=None):
    if not doctypes:
        doctypes = self.DOCTYPES
    for doctype in doctypes:
        self.unset_default_viewer(doctype)
    return self
</t>
<t tx="ekr.20241206101345.376">def unset_default_viewer(self, doctype):
    viewers = self.get_viewers()
    if doctype in viewers:
        del viewers[doctype]
</t>
<t tx="ekr.20241206101345.377">def write(self):
    """
    write the labconfig settings file
    """
    self.settings_file.parent.mkdir(parents=True, exist_ok=True)
    with self.settings_file.open("w") as fid:
        json.dump(self.config, fid, indent=2)
</t>
<t tx="ekr.20241206101345.378">"""`jupytext` as a command line tool"""

import argparse
import glob
import json
import os
import re
import shlex
import subprocess
import sys
import warnings
from copy import copy
from tempfile import NamedTemporaryFile

from .combine import combine_inputs_with_outputs
from .compare import NotebookDifference, compare, test_round_trip_conversion
from .config import load_jupytext_config, notebook_formats
from .formats import (
    _BINARY_FORMAT_OPTIONS,
    _VALID_FORMAT_OPTIONS,
    JUPYTEXT_FORMATS,
    check_auto_ext,
    check_file_version,
    long_form_multiple_formats,
    long_form_one_format,
    short_form_one_format,
)
from .header import recursive_update
from .jupytext import create_prefix_dir, read, reads, write, writes
from .kernels import find_kernel_specs, get_kernel_spec, kernelspec_from_language
from .languages import _SCRIPT_EXTENSIONS
from .paired_paths import (
    InconsistentPath,
    base_path,
    find_base_path_and_format,
    full_path,
    paired_paths,
)
from .pairs import latest_inputs_and_outputs, read_pair, write_pair
from .version import __version__


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.379">def system(*args, **kwargs):
    """Execute the given bash command"""
    kwargs.setdefault("stdout", subprocess.PIPE)
    proc = subprocess.Popen(args, **kwargs)
    out, _ = proc.communicate()
    if proc.returncode:
        raise SystemExit(proc.returncode)
    return out.decode("utf-8")
</t>
<t tx="ekr.20241206101345.38">def try_eval_metadata(metadata, name):
    """Evaluate the metadata to a python object, if possible"""
    value = metadata[name]
    if not isinstance(value, str):
        return
    if (value.startswith('"') and value.endswith('"')) or (
        value.startswith("'") and value.endswith("'")
    ):
        metadata[name] = value[1:-1]
        return
    if value.startswith("c(") and value.endswith(")"):
        value = "[" + value[2:-1] + "]"
    elif value.startswith("list(") and value.endswith(")"):
        value = "[" + value[5:-1] + "]"
    try:
        metadata[name] = ast.literal_eval(value)
    except (SyntaxError, ValueError):
        if name != "name":
            metadata[name] = "#R_CODE#" + value
        return
</t>
<t tx="ekr.20241206101345.380">def tool_version(tool):
    try:
        args = tool.split(" ")
        args.append("--version")
        return system(*args)
    except (OSError, SystemExit):  # pragma: no cover
        return None
</t>
<t tx="ekr.20241206101345.381">def str2bool(value):
    """Parse Yes/No/Default string
    https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse"""
    if value.lower() in ("yes", "true", "t", "y", "1"):
        return True
    if value.lower() in ("no", "false", "f", "n", "0"):
        return False
    if value.lower() in ("d", "default", ""):
        return None
    raise argparse.ArgumentTypeError("Expected: (Y)es/(T)rue/(N)o/(F)alse/(D)efault")
</t>
<t tx="ekr.20241206101345.382">def parse_jupytext_args(args=None):
    """Command line parser for jupytext"""

    parser = argparse.ArgumentParser(
        description="Jupyter Notebooks as Markdown Documents, Julia, Python or R Scripts",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    # Input
    parser.add_argument(
        "notebooks",
        help="One or more notebook(s). "
        "Notebook is read from stdin when this argument is empty.",
        nargs="*",
    )
    parser.add_argument(
        "--from",
        dest="input_format",
        help="Jupytext format for the input(s). Inferred from the "
        "file extension and content when missing.",
    )
    # Destination format &amp; act on metadata
    parser.add_argument(
        "--to",
        dest="output_format",
        help=(
            "The destination format: 'ipynb', 'markdown' or 'script', or a file extension: "
            "'md', 'Rmd', 'jl', 'py', 'R', ..., 'auto' (script extension matching the notebook language), "
            "or a combination of an extension and a format name, e.g. {} ".format(
                ", ".join(
                    {
                        f"md:{fmt.format_name}"
                        for fmt in JUPYTEXT_FORMATS
                        if fmt.extension == ".md"
                    }
                )
            )
            + " or {}. ".format(
                ", ".join(
                    {
                        f"py:{fmt.format_name}"
                        for fmt in JUPYTEXT_FORMATS
                        if fmt.extension == ".py"
                    }
                )
            )
            + "The default format for scripts is the 'light' format, "
            "which uses few cell markers (none when possible). "
            "Alternatively, a format compatible with many editors is the "
            "'percent' format, which uses '# %%%%' as cell markers. "
            "The main formats (markdown, light, percent) preserve "
            "notebooks and text documents in a roundtrip. Use the "
            "--test and and --test-strict commands to test the roundtrip on your files. "
            "Read more about the available formats at "
            "https://jupytext.readthedocs.io/en/latest/formats.html"
        ),
    )

    # Destination file
    parser.add_argument(
        "-o",
        "--output",
        help="Destination file. Defaults to the original file, "
        "with prefix/suffix/extension changed according to "
        "the destination format. "
        "Use '-' to print the notebook on stdout.",
    )
    parser.add_argument(
        "--update",
        action="store_true",
        help="Preserve the output cells when the destination "
        "notebook is an .ipynb file that already exists",
    )

    parser.add_argument(
        "--set-formats",
        type=str,
        help="Turn the notebook or text document to one or more alternative representations "
        "with e.g. '--set-formats ipynb,py:light'. "
        "The --set-formats option also triggers the creation/update of all paired files",
    )

    # Action: convert(default)/version/list paired paths/sync/apply/test
    action = parser.add_mutually_exclusive_group()
    action.add_argument(
        "--sync",
        "-s",
        help="Synchronize the content of the paired representations of "
        "the given notebook. Input cells are taken from the file that "
        "was last modified, and outputs are read from the ipynb file, "
        "if present.",
        action="store_true",
    )
    action.add_argument(
        "--paired-paths",
        "-p",
        help="List the locations of the alternative representations for this notebook.",
        action="store_true",
    )
    parser.add_argument(
        "--format-options",
        "--opt",
        action="append",
        help="Set format options with e.g. "
        "'--opt comment_magics=true' or '--opt notebook_metadata_filter=-kernelspec'.",
    )
    parser.add_argument(
        "--update-metadata",
        default={},
        type=json.loads,
        help="Update the notebook metadata with the desired dictionary. "
        "Argument must be given in JSON format. For instance, if you "
        "want to activate a pairing in the generated file, use e.g. "
        """--update-metadata '{"jupytext":{"formats":"ipynb,py:light"}}' """
        "See also the --opt and --set-formats options for other ways "
        "to operate on the Jupytext metadata.",
    )
    parser.add_argument(
        "--use-source-timestamp",
        help="Set the modification timestamp of the output file(s) equal"
        "to that of the source file, and keep the source file and "
        "its timestamp unchanged.",
        action="store_true",
    )
    parser.add_argument(
        "--warn-only",
        "-w",
        action="store_true",
        help="Only issue a warning and continue processing other notebooks "
        "when the conversion of a given notebook fails",
    )
    action.add_argument(
        "--test",
        action="store_true",
        help="Test that the notebook is stable under a round trip conversion, "
        "up to the expected changes",
    )
    action.add_argument(
        "--test-strict",
        action="store_true",
        help="Test that the notebook is strictly stable under a round trip conversion",
    )
    parser.add_argument(
        "--stop",
        "-x",
        dest="stop_on_first_error",
        action="store_true",
        help="In --test mode, stop on first round trip conversion error, and report stack traceback",
    )

    # Pipe notebook inputs into other commands
    parser.add_argument(
        "--pipe",
        action="append",
        help="Pipe the text representation (in format --pipe-fmt) of the notebook into "
        "another program, and read the notebook back. For instance, reformat "
        "your notebook with: "
        "'jupytext notebook.ipynb --pipe black' "
        "If you want to reformat it and sync the paired representation, execute: "
        "'jupytext notebook.ipynb --sync --pipe black' "
        "In case the program that you want to execute does not accept pipes, use {} "
        "as a placeholder for a temporary file name into which jupytext will "
        "write the text representation of the notebook, e.g.: "
        "jupytext notebook.ipynb --pipe 'black {}'",
    )
    parser.add_argument(
        "--diff",
        "-d",
        action="store_true",
        help="Show the differences between (the inputs) of two notebooks",
    )
    parser.add_argument(
        "--diff-format",
        help="The text format used to show differences in --diff",
    )
    parser.add_argument(
        "--check",
        action="append",
        help="Pipe the text representation (in format --pipe-fmt) of the notebook into "
        "another program, and test that the returned value is non zero. For "
        "instance, test that your notebook is pep8 compliant with: "
        "'jupytext notebook.ipynb --check flake8' "
        "or run pytest on your notebook with: "
        "'jupytext notebook.ipynb --check pytest' "
        "In case the program that you want to execute does not accept pipes, use {} "
        "as a placeholder for a temporary file name into which jupytext will "
        "write the text representation of the notebook, e.g.: "
        "jupytext notebook.ipynb --check 'pytest {}'",
    )
    parser.add_argument(
        "--pipe-fmt",
        default="auto:percent",
        help="The format in which the notebook should be piped to other programs, "
        "when using the --pipe and/or --check commands.",
    )

    # Execute the notebook
    parser.add_argument(
        "--set-kernel",
        "-k",
        type=str,
        help="Set the kernel with the given name on the notebook. "
        "Use '--set-kernel -' to set a kernel matching the current "
        "environment on Python notebooks, and matching the notebook "
        "language otherwise (get the list of available kernels with "
        "'jupyter kernelspec list')",
    )
    parser.add_argument(
        "--execute",
        action="store_true",
        help="Execute the notebook with the given kernel. In the "
        "--pre-commit-mode, the notebook is executed only if a code "
        "cell changed, or if some execution outputs are missing "
        "or not ordered.",
    )
    parser.add_argument(
        "--run-path",
        type=str,
        help="Execute the notebook at the given path (defaults to the notebook parent directory)",
    )

    parser.add_argument(
        "--quiet",
        "-q",
        action="store_true",
        help="Quiet mode: do not comment about files being updated or created",
    )
    parser.add_argument(
        "--show-changes",
        action="store_true",
        help="Display the diff for each output file",
    )

    action.add_argument(
        "--version",
        "-v",
        action="store_true",
        help="Show jupytext's version number and exit",
    )

    parser.add_argument(
        "--pre-commit",
        action="store_true",
        help="Ignore the notebook argument, and instead apply Jupytext "
        "on the notebooks found in the git index, which have an "
        "extension that matches the (optional) --from argument.",
    )
    parser.add_argument(
        "--pre-commit-mode",
        action="store_true",
        help="This is a mode that is compatible with the pre-commit framework. "
        "In this mode, --sync won't use timestamp but instead will "
        "determines the source notebook as the element of the pair "
        "that is added to the git index. An alert is raised if multiple inconsistent representations are "
        "in the index. It also raises an alert after updating the paired files or outputs if those "
        "files need to be added to the index. Finally, filepaths that aren't in the source format "
        "you are trying to convert from are ignored.",
    )

    return parser.parse_args(args)
</t>
<t tx="ekr.20241206101345.385">def notebooks_in_git_index(fmt):
    """Return the list of modified and deleted ipynb files in the git index that match the given format"""
    git_status = system("git", "status", "--porcelain")
    re_modified = re.compile(r"^[AM]+\s+(?P&lt;name&gt;.*)", re.MULTILINE)
    modified_files_in_git_index = re_modified.findall(git_status)
    files = []
    for nb_file in modified_files_in_git_index:
        if nb_file.startswith('"') and nb_file.endswith('"'):
            nb_file = nb_file[1:-1]
        try:
            base_path(nb_file, fmt)
            files.append(nb_file)
        except InconsistentPath:
            continue
    return files
</t>
<t tx="ekr.20241206101345.386">def is_untracked(filepath):
    """Check whether a file was created or modified and needs to be added to the git index"""
    if not filepath:
        return False

    output = system("git", "ls-files", filepath).strip()
    if output == "":
        return True

    output = system("git", "diff", filepath).strip()
    if output != "":
        return True

    return False
</t>
<t tx="ekr.20241206101345.387">def print_paired_paths(nb_file, fmt):
    """Display the paired paths for this notebook"""
    notebook = read(nb_file, fmt=fmt)
    formats = notebook.metadata.get("jupytext", {}).get("formats")
    if formats:
        for path, _ in paired_paths(nb_file, fmt, formats):
            if path != nb_file:
                sys.stdout.write(path + "\n")
</t>
<t tx="ekr.20241206101345.388">def set_format_options(fmt, format_options):
    """Apply the desired format options to the format description fmt"""
    if not format_options:
        return

    for opt in format_options:
        try:
            key, value = opt.split("=")
        except ValueError as err:
            raise ValueError(
                "Format options are expected to be of the form key=value, not '{}'".format(
                    opt
                )
            ) from err

        if key not in _VALID_FORMAT_OPTIONS:
            raise ValueError(
                "'{}' is not a valid format option. Expected one of '{}'".format(
                    key, "', '".join(_VALID_FORMAT_OPTIONS)
                )
            )

        if key in _BINARY_FORMAT_OPTIONS:
            value = str2bool(value)

        fmt[key] = value
</t>
<t tx="ekr.20241206101345.389">def set_prefix_and_suffix(fmt, formats, nb_file):
    """Add prefix and suffix information from jupytext.formats if format and path matches"""
    for alt_fmt in long_form_multiple_formats(formats):
        if alt_fmt["extension"] == fmt["extension"] and fmt.get(
            "format_name"
        ) == alt_fmt.get("format_name"):
            try:
                base_path(nb_file, alt_fmt)
                fmt.update(alt_fmt)
                return
            except InconsistentPath:
                continue
</t>
<t tx="ekr.20241206101345.39">def is_active(ext, metadata, default=True):
    """Is the cell active for the given file extension?"""
    if metadata.get("run_control", {}).get("frozen") is True:
        return ext == ".ipynb"
    for tag in metadata.get("tags", []):
        if tag.startswith("active-"):
            return ext.replace(".", "") in tag.split("-")
    if "active" not in metadata:
        return default
    return ext.replace(".", "") in re.split(r"\.|,", metadata["active"])
</t>
<t tx="ekr.20241206101345.390">class NotAPairedNotebook(ValueError):
    """An error raised when a notebook is not a paired notebook"""
</t>
<t tx="ekr.20241206101345.391">class InconsistentVersions(ValueError):
    """An error raised when two paired files in the git index contain inconsistent representations"""
</t>
<t tx="ekr.20241206101345.392">def file_in_git_index(path):
    if not os.path.isfile(path):
        return False
    return system("git", "status", "--porcelain", path).strip().startswith(("M", "A"))
</t>
<t tx="ekr.20241206101345.393">def git_timestamp(path):
    if not os.path.isfile(path):
        return None

    # Files that are in the git index are considered most recent
    if file_in_git_index(path):
        return float("inf")

    # Return the commit timestamp
    try:
        git_ts_str = system("git", "log", "-1", "--pretty=%ct", path).strip()
    except SystemExit as err:
        if err.code == 128:
            # git not initialized
            git_ts_str = ""
        else:
            raise

    if git_ts_str:
        return float(git_ts_str)

    # The file is not in the git index
    return get_timestamp(path)
</t>
<t tx="ekr.20241206101345.394">def get_timestamp(path):
    if not os.path.isfile(path):
        return None
    return os.lstat(path).st_mtime
</t>
<t tx="ekr.20241206101345.395">def load_paired_notebook(notebook, fmt, config, formats, nb_file, log, pre_commit_mode):
    """Update the notebook with the inputs and outputs of the most recent paired files"""
    if not formats:
        raise NotAPairedNotebook(f"{shlex.quote(nb_file)} is not a paired notebook")

    formats = long_form_multiple_formats(formats)
    _, fmt_with_prefix_suffix = find_base_path_and_format(nb_file, formats)
    fmt.update(fmt_with_prefix_suffix)

    def read_one_file(path, fmt):
        if path == nb_file:
            return notebook

        log(f"[jupytext] Loading {shlex.quote(path)}")
        return read(path, fmt=fmt, config=config)

    if pre_commit_mode and file_in_git_index(nb_file):
        # We raise an error if two representations of this notebook in the git index are inconsistent
        nb_files_in_git_index = sorted(
            (
                (alt_path, alt_fmt)
                for alt_path, alt_fmt in paired_paths(nb_file, fmt, formats)
                if file_in_git_index(alt_path)
            ),
            key=lambda x: 0 if x[1]["extension"] != ".ipynb" else 1,
        )

        if len(nb_files_in_git_index) &gt; 1:
            path0, fmt0 = nb_files_in_git_index[0]
            with open(path0, encoding="utf-8") as fp:
                text0 = fp.read()
            for alt_path, alt_fmt in nb_files_in_git_index[1:]:
                nb = read(alt_path, fmt=alt_fmt, config=config)
                alt_text = writes(nb, fmt=fmt0, config=config)
                if alt_text != text0:
                    diff = compare(alt_text, text0, alt_path, path0, return_diff=True)
                    raise InconsistentVersions(
                        f"{shlex.quote(alt_path)} and {shlex.quote(path0)} are inconsistent.\n"
                        + diff
                        + f"\nPlease revert JUST ONE of the files with EITHER\n"
                        f"    git reset {shlex.quote(alt_path)} &amp;&amp; git checkout -- {shlex.quote(alt_path)}\nOR\n"
                        f"    git reset {shlex.quote(path0)} &amp;&amp; git checkout -- {shlex.quote(path0)}\n"
                    )

    inputs, outputs = latest_inputs_and_outputs(
        nb_file, fmt, formats, git_timestamp if pre_commit_mode else get_timestamp
    )
    notebook = read_pair(inputs, outputs, read_one_file)

    return notebook, inputs.path, outputs.path
</t>
<t tx="ekr.20241206101345.396">def exec_command(command, input=None, capture=False, warn_only=False):
    """Execute the desired command, and pipe the given input into it"""
    assert isinstance(command, list)
    sys.stdout.write("[jupytext] Executing {}\n".format(" ".join(command)))
    process = subprocess.Popen(
        command,
        **(
            dict(stdout=subprocess.PIPE, stdin=subprocess.PIPE)
            if input is not None
            else {}
        ),
    )
    out, err = process.communicate(input=input)
    if out and not capture:
        sys.stdout.write(out.decode("utf-8"))
    if err:
        sys.stderr.write(err.decode("utf-8"))

    if process.returncode:
        msg = f"The command '{' '.join(command)}' exited with code {process.returncode}"
        hint = (
            "" if warn_only else " (use --warn-only to turn this error into a warning)"
        )
        sys.stderr.write(
            f"[jupytext] {'Warning' if warn_only else 'Error'}: {msg}{hint}\n"
        )
        if not warn_only:
            raise SystemExit(process.returncode)

    return out
</t>
<t tx="ekr.20241206101345.397">def pipe_notebook(
    notebook,
    command,
    fmt="py:percent",
    update=True,
    prefix=None,
    directory=None,
    warn_only=False,
):
    """Pipe the notebook, in the desired representation, to the given command. Update the notebook
    with the returned content if desired."""
    if command in ["black", "flake8", "autopep8"]:
        command = command + " -"
    elif command in ["pytest", "unittest"]:
        command = command + " {}"

    fmt = long_form_one_format(
        fmt, notebook.metadata, auto_ext_requires_language_info=False
    )
    fmt = check_auto_ext(fmt, notebook.metadata, "--pipe-fmt")
    text = writes(notebook, fmt)

    command = shlex.split(command)
    if "{}" in command:
        if prefix is not None:
            prefix = prefix + (" " if " " in prefix else "_")
        tmp_file_args = dict(
            mode="w+",
            encoding="utf8",
            prefix=prefix,
            suffix=fmt["extension"],
            dir=directory,
            delete=False,
        )
        try:
            tmp = NamedTemporaryFile(**tmp_file_args)
        except TypeError:
            # NamedTemporaryFile does not have an 'encoding' argument on pypy
            tmp_file_args.pop("encoding")
            tmp = NamedTemporaryFile(**tmp_file_args)
        try:
            tmp.write(text)
            tmp.close()

            exec_command(
                [cmd if cmd != "{}" else tmp.name for cmd in command],
                capture=update,
                warn_only=warn_only,
            )

            if not update:
                return notebook

            piped_notebook = read(tmp.name, fmt=fmt)
        finally:
            os.remove(tmp.name)
    else:
        cmd_output = exec_command(
            command, text.encode("utf-8"), capture=update, warn_only=warn_only
        )

        if not update:
            return notebook

        if not cmd_output:
            sys.stderr.write(
                "[jupytext] The command '{}' had no output. As a result, the notebook is empty. "
                "Is this expected? If not, use --check rather than --pipe for this command.".format(
                    command
                )
            )

        piped_notebook = reads(cmd_output.decode("utf-8"), fmt)

    if fmt["extension"] != ".ipynb":
        piped_notebook = combine_inputs_with_outputs(piped_notebook, notebook, fmt)

    # Remove jupytext / text_representation entry
    if "jupytext" in notebook.metadata:
        piped_notebook.metadata["jupytext"] = notebook.metadata["jupytext"]
    else:
        piped_notebook.metadata.pop("jupytext", None)

    return piped_notebook
</t>
<t tx="ekr.20241206101345.398">def execution_counts_are_in_order(notebook):
    """Returns True if all the code cells have an execution count, ordered from 1 to N with no missing number"""
    expected_execution_count = 1
    for cell in notebook.cells:
        if cell.cell_type == "code":
            if cell.execution_count != expected_execution_count:
                return False
            expected_execution_count += 1
    return True
</t>
<t tx="ekr.20241206101345.399">def code_cells_have_changed(notebook, nb_files):
    """The source for the code cells has not changed"""
    for nb_file in nb_files:
        if not os.path.exists(nb_file):
            return True

        nb_ref = read(nb_file)

        # Are the new code cells equals to those in the file?
        ref = [cell.source for cell in nb_ref.cells if cell.cell_type == "code"]
        new = [cell.source for cell in notebook.cells if cell.cell_type == "code"]

        if ref != new:
            return True

    return False
</t>
<t tx="ekr.20241206101345.4">@language nim

sub: RootObj = huh #
n: Natural = range(0..9)
r: Rune = 'é'
a: any = none
b: bool = false
s: string = "Single string"
f1: float64 = 0.0d
f2: float32 = -.0f32
f3: float = 1f+1
i: int = 1_000_000
u'abd = 2u32
var x = 5'u4
c = 'abc'

#[ A multi-line comment.
    #[
        Blah.
    ]#
]#

# GitHub colors all other constants blue, including system constants:
42
3.5
AllocStats
DivByZeroError
BiggestUInt
Slice
SomeFloat

# GitHub colors system functions purple:
echo "Hi"
alloc()

# GitHub does not color modules that "system" imports.
import exceptions  # Imported by system module.
import Exception  # Defined in system module.
</t>
<t tx="ekr.20241206101345.40">def metadata_to_double_percent_options(metadata, plain_json):
    """Metadata to double percent lines"""
    text = []
    if "title" in metadata:
        text.append(metadata.pop("title"))
    if "cell_depth" in metadata:
        text.insert(0, "%" * metadata.pop("cell_depth"))
    if "cell_type" in metadata:
        text.append(
            "[{}]".format(metadata.pop("region_name", metadata.pop("cell_type")))
        )
    return metadata_to_text(" ".join(text), metadata, plain_json=plain_json)
</t>
<t tx="ekr.20241206101345.401">def jupytext(args=None):
    """Entry point for the jupytext script"""
    args = parse_jupytext_args(args)

    def log(text):
        if not args.quiet:
            sys.stdout.write(text + "\n")

    if args.version:
        log(__version__)
        return 0

    if args.pre_commit:
        warnings.warn(
            "The --pre-commit argument is deprecated. "
            "Please consider switching to the pre-commit.com framework "
            "(let us know at https://github.com/mwouts/jupytext/issues "
            "if that is an issue for you)",
            DeprecationWarning,
        )
        if args.notebooks:
            raise ValueError(
                "--pre-commit takes notebooks from the git index. Do not pass any notebook here."
            )
        args.notebooks = notebooks_in_git_index(args.input_format)
        log("[jupytext] Notebooks in git index are:")
        for nb_file in args.notebooks:
            log(nb_file)

    # Read notebook from stdin
    if not args.notebooks:
        if not args.pre_commit:
            args.notebooks = ["-"]

    if args.set_formats is not None:
        # Replace empty string with None
        args.update_metadata = recursive_update(
            args.update_metadata, {"jupytext": {"formats": args.set_formats or None}}
        )
        args.sync = True

    if args.paired_paths:
        if len(args.notebooks) != 1:
            raise ValueError("--paired-paths applies to a single notebook")
        print_paired_paths(args.notebooks[0], args.input_format)
        return 1

    if args.run_path:
        args.execute = True

    if (
        (args.test or args.test_strict)
        and not args.output_format
        and not args.output
        and not args.sync
    ):
        raise ValueError("Please provide one of --to, --output or --sync")

    if (
        not args.output_format
        and not args.output
        and not args.sync
        and not args.pipe
        and not args.diff
        and not args.check
        and not args.update_metadata
        and not args.format_options
        and not args.set_kernel
        and not args.execute
    ):
        raise ValueError(
            "Please provide one of --to, --output, --set-formats, --sync, --pipe, --diff, "
            "--check, --update-metadata, --format-options, --set-kernel or --execute"
        )

    if args.diff:
        if (
            len(args.notebooks) != 2
            or args.output_format
            or args.output
            or args.sync
            or args.pipe
            or args.check
            or args.update_metadata
            or args.format_options
            or args.set_kernel
            or args.execute
        ):
            raise ValueError(
                "Please provide two notebooks after 'jupytext --diff'.\n"
                "NB: Use --show-changes if you wish to see the changes in "
                "a notebook being updated by Jupytext."
            )

        nb_file1, nb_file2 = args.notebooks
        nb1 = read(nb_file1)
        nb2 = read(nb_file2)

        def fmt_if_not_ipynb(nb):
            fmt = nb.metadata["jupytext"]["text_representation"]
            if fmt["extension"] == ".ipynb":
                return None
            return short_form_one_format(fmt)

        diff_fmt = (
            args.diff_format or fmt_if_not_ipynb(nb1) or fmt_if_not_ipynb(nb2) or "md"
        )

        diff = compare(
            writes(nb2, diff_fmt),
            writes(nb1, diff_fmt),
            nb_file2,
            nb_file1,
            return_diff=True,
        )
        sys.stdout.write(diff)

        return

    if args.output and len(args.notebooks) != 1:
        raise ValueError("Please input a single notebook when using --output")

    # Warn if '--to' is used in place of '--output'
    if (
        not args.output
        and args.output_format
        and "." in args.output_format
        # a suffix is expected to start with one of these characters #901
        and not args.output_format.startswith((".", "-", "_"))
        and "//" not in args.output_format
    ):

        def single_line(msg, *args, **kwargs):
            return f"[warning] {msg}\n"

        warnings.formatwarning = single_line
        warnings.warn(
            "You might have passed a file name to the '--to' option, "
            "when a format description was expected. Maybe you want to use the '-o' option instead?"
        )

    if args.input_format:
        args.input_format = long_form_one_format(args.input_format)

    if args.output_format:
        args.output_format = long_form_one_format(args.output_format)
        set_format_options(args.output_format, args.format_options)

    # Wildcard extension on Windows #202
    notebooks = []
    for pattern in args.notebooks:
        if "*" in pattern or "?" in pattern:
            # Exclude the .jupytext.py configuration file
            notebooks.extend(glob.glob(pattern, recursive=True))
        else:
            notebooks.append(pattern)

    # Count how many file have round-trip issues when testing
    exit_code = 0
    for nb_file in notebooks:
        if not args.warn_only:
            exit_code += jupytext_single_file(nb_file, args, log)
        else:
            try:
                exit_code += jupytext_single_file(nb_file, args, log)
            except Exception as err:
                sys.stderr.write(f"[jupytext] Error: {str(err)}\n")

    return exit_code
</t>
<t tx="ekr.20241206101345.402">def jupytext_single_file(nb_file, args, log):
    """Apply the jupytext command, with given arguments, to a single file"""
    if nb_file == "-" and args.sync:
        msg = "Missing notebook path."
        if args.set_formats is not None and os.path.isfile(args.set_formats):
            msg += f" Maybe you mean 'jupytext --sync {args.set_formats}' ?"
        raise ValueError(msg)

    nb_dest = None
    if args.output:
        nb_dest = args.output
    elif nb_file == "-":
        nb_dest = "-"
    else:
        try:
            bp = base_path(nb_file, args.input_format)
        except InconsistentPath:
            if args.pre_commit_mode:
                log(
                    "[jupytext] Ignoring unmatched input path {}{}".format(
                        nb_file,
                        f" for format {args.input_format}" if args.input_format else "",
                    )
                )
                return 0
            raise
        if args.output_format:
            nb_dest = full_path(bp, args.output_format)

    config = load_jupytext_config(os.path.abspath(nb_file))

    # Just acting on metadata / pipe =&gt; save in place
    save_in_place = not nb_dest and not args.sync
    if save_in_place:
        nb_dest = nb_file

    if nb_dest == "-":
        args.quiet = True

    # I. ### Read the notebook ###
    fmt = copy(args.input_format) or {}
    if not fmt:
        ext = os.path.splitext(nb_file)[1]
        if ext:
            fmt = {"extension": ext}
    if fmt:
        set_format_options(fmt, args.format_options)
    log(
        "[jupytext] Reading {}{}".format(
            nb_file if nb_file != "-" else "stdin",
            f" in format {short_form_one_format(fmt)}" if "extension" in fmt else "",
        )
    )

    notebook = read(nb_file, fmt=fmt, config=config)
    if "extension" in fmt and "format_name" not in fmt:
        text_representation = notebook.metadata.get("jupytext", {}).get(
            "text_representation", {}
        )
        if text_representation.get("extension") == fmt["extension"]:
            fmt["format_name"] = text_representation["format_name"]

    # Compute actual extension when using script/auto, and update nb_dest if necessary
    dest_fmt = args.output_format
    if dest_fmt and dest_fmt["extension"] == ".auto":
        dest_fmt = check_auto_ext(dest_fmt, notebook.metadata, "--to")
        if not args.output and nb_file != "-":
            nb_dest = full_path(base_path(nb_file, args.input_format), dest_fmt)

    # Set the kernel
    set_kernel = args.set_kernel
    if (
        (not set_kernel)
        and args.execute
        and notebook.metadata.get("kernelspec", {}).get("name") is None
    ):
        set_kernel = "-"

    if set_kernel:
        if set_kernel == "-":
            language = (
                notebook.metadata.get("jupytext", {}).get("main_language")
                or notebook.metadata["kernelspec"]["language"]
            )

            if not language:
                raise ValueError(
                    "Cannot infer a kernel as notebook language is not defined"
                )

            kernelspec = kernelspec_from_language(language)
        else:
            try:
                kernelspec = get_kernel_spec(set_kernel)
            except KeyError as err:
                raise KeyError(
                    "Please choose a kernel name among {}".format(
                        find_kernel_specs().keys()
                    )
                ) from err

            kernelspec = {
                "name": args.set_kernel,
                "language": kernelspec.language,
                "display_name": kernelspec.display_name,
            }

        log("[jupytext] Setting kernel {}".format(kernelspec.get("name")))
        args.update_metadata["kernelspec"] = kernelspec

    # Are we updating a text file that has a metadata filter? #212
    if args.update_metadata or args.format_options:
        if (
            notebook.metadata.get("jupytext", {}).get("notebook_metadata_filter")
            == "-all"
        ):
            notebook.metadata.get("jupytext", {}).pop("notebook_metadata_filter")

    # Update the metadata
    if args.update_metadata:
        log(
            "[jupytext] Updating notebook metadata with '{}'".format(
                json.dumps(args.update_metadata)
            )
        )

        if (
            "kernelspec" in args.update_metadata
            and "main_language" in notebook.metadata.get("jupytext", {})
        ):
            notebook.metadata["jupytext"].pop("main_language")

        recursive_update(notebook.metadata, args.update_metadata)

    # Read paired notebooks
    nb_files = [nb_file, nb_dest]
    if args.sync:
        formats = notebook_formats(
            notebook, config, nb_file, fallback_on_current_fmt=False
        )
        set_prefix_and_suffix(fmt, formats, nb_file)
        try:
            notebook, inputs_nb_file, outputs_nb_file = load_paired_notebook(
                notebook, fmt, config, formats, nb_file, log, args.pre_commit_mode
            )
            nb_files = [inputs_nb_file, outputs_nb_file]
        except NotAPairedNotebook as err:
            sys.stderr.write("[jupytext] Warning: " + str(err) + "\n")
            return 0
        except InconsistentVersions as err:
            sys.stderr.write("[jupytext] Error: " + str(err) + "\n")
            return 1

    # II. ### Apply commands onto the notebook ###
    # Pipe the notebook into the desired commands
    if nb_file == "-":
        prefix = None
        directory = None
    else:
        prefix = os.path.splitext(os.path.basename(nb_file))[0]
        directory = os.path.dirname(nb_file)
    for cmd in args.pipe or []:
        notebook = pipe_notebook(
            notebook,
            cmd,
            args.pipe_fmt,
            prefix=prefix,
            directory=directory,
            warn_only=args.warn_only,
        )

    # and/or test the desired commands onto the notebook
    for cmd in args.check or []:
        pipe_notebook(
            notebook,
            cmd,
            args.pipe_fmt,
            update=False,
            prefix=prefix,
            directory=directory,
            warn_only=args.warn_only,
        )

    if (
        args.execute
        and args.pre_commit_mode
        and execution_counts_are_in_order(notebook)
        and not code_cells_have_changed(notebook, nb_files)
    ):
        log(
            f"[jupytext] Execution of {shlex.quote(nb_file)} "
            f"skipped as code cells have not changed and outputs are present."
        )
        args.execute = False

    # Execute the notebook
    if args.execute:
        kernel_name = notebook.metadata.get("kernelspec", {}).get("name")
        log(f"[jupytext] Executing notebook with kernel {kernel_name}")

        if nb_dest is not None and nb_dest != "-":
            nb_path = os.path.dirname(nb_dest)
        elif nb_file != "-":
            nb_path = os.path.dirname(nb_file)
        else:
            nb_path = None

        run_path = args.run_path or nb_path
        if args.run_path and not os.path.isdir(run_path):
            # is this a relative directory?
            for base_dir in [nb_path, os.getcwd()]:
                try_path = os.path.join(base_dir, run_path)
                if os.path.isdir(try_path):
                    run_path = try_path
                    break
            if not os.path.isdir(run_path):
                raise ValueError(f"--run-path={args.run_path} is not a valid path")

        if run_path:
            resources = {"metadata": {"path": run_path}}
        else:
            resources = {}

        try:
            from nbconvert.preprocessors import ExecutePreprocessor

            exec_proc = ExecutePreprocessor(timeout=None, kernel_name=kernel_name)
            exec_proc.preprocess(notebook, resources=resources)
        except (ImportError, RuntimeError) as err:
            if args.pre_commit_mode:
                raise RuntimeError(
                    "An error occurred while executing the notebook. Please "
                    "make sure that you have listed 'nbconvert' and 'ipykernel' "
                    "under 'additional_dependencies' in the jupytext hook."
                ) from err
            raise RuntimeError(
                "An error occurred while executing the notebook. Please "
                "make sure that 'nbconvert' and 'ipykernel' are installed."
            ) from err

    # III. ### Possible actions ###
    # a. Test round trip conversion
    if args.test or args.test_strict:
        try:
            # Round trip from an ipynb document
            if fmt["extension"] == ".ipynb":
                test_round_trip_conversion(
                    notebook,
                    dest_fmt,
                    update=args.update,
                    allow_expected_differences=not args.test_strict,
                    stop_on_first_error=args.stop_on_first_error,
                )

            # Round trip from a text file
            else:
                with open(nb_file, encoding="utf-8") as fp:
                    org_text = fp.read()

                # If the destination is not ipynb, we convert to/back that format
                if dest_fmt["extension"] != ".ipynb":
                    dest_text = writes(notebook, fmt=dest_fmt)
                    notebook = reads(dest_text, fmt=dest_fmt)

                text = writes(notebook, fmt=fmt, config=config)

                if args.test_strict:
                    compare(text, org_text)
                else:
                    # we ignore the YAML header in the comparison #414
                    comment = _SCRIPT_EXTENSIONS.get(fmt["extension"], {}).get(
                        "comment", ""
                    )
                    # white spaces between the comment char and the YAML delimiters are allowed
                    if comment:
                        comment = comment + r"\s*"
                    yaml_header = re.compile(
                        r"^{comment}---\s*\n.*\n{comment}---\s*\n".format(
                            comment=comment
                        ),
                        re.MULTILINE | re.DOTALL,
                    )
                    compare(
                        re.sub(yaml_header, "", text), re.sub(yaml_header, "", org_text)
                    )

        except (NotebookDifference, AssertionError) as err:
            sys.stdout.write(f"{nb_file}: {str(err)}")
            return 1
        return 0

    # b. Output to the desired file or format
    untracked_files = 0

    def lazy_write(path, fmt=None, action=None, update_timestamp_only=False):
        """Write the notebook only if it has changed"""
        if path == "-":
            write(notebook, "-", fmt=fmt)
            return

        nonlocal untracked_files
        if update_timestamp_only:
            modified = False
        else:
            _, ext = os.path.splitext(path)
            fmt = copy(fmt or {})
            fmt = long_form_one_format(fmt, update={"extension": ext})
            new_content = writes(notebook, fmt=fmt, config=config)
            diff = None
            if not new_content.endswith("\n"):
                new_content += "\n"
            if not os.path.isfile(path):
                modified = True
            else:
                with open(path, encoding="utf-8") as fp:
                    current_content = fp.read()
                modified = new_content != current_content
                if modified and args.show_changes:
                    diff = compare(
                        new_content,
                        current_content,
                        "",
                        "",
                        return_diff=True,
                    )

        if modified:
            # The text representation of the notebook has changed, we write it on disk
            if action is None:
                message = f"[jupytext] Updating {shlex.quote(path)}"
            else:
                message = "[jupytext] Writing {path}{format}{action}".format(
                    path=shlex.quote(path),
                    format=" in format " + short_form_one_format(fmt)
                    if fmt and "format_name" in fmt
                    else "",
                    action=action,
                )
            if diff is not None:
                message += " with this change:\n" + diff

            log(message)
            create_prefix_dir(path, fmt)
            with open(path, "w", encoding="utf-8") as fp:
                fp.write(new_content)

        # Otherwise, we only update the timestamp of the text file to make sure
        # they remain more recent than the ipynb file, for compatibility with the
        # Jupytext contents manager for Jupyter
        if args.use_source_timestamp:
            log(
                f"[jupytext] Setting the timestamp of {shlex.quote(path)} equal to that of {shlex.quote(nb_file)}"
            )
            os.utime(path, (os.stat(path).st_atime, os.stat(nb_file).st_mtime))
        elif not modified:
            if path.endswith(".ipynb"):
                # No need to update the timestamp of ipynb files
                log(f"[jupytext] Unchanged {shlex.quote(path)}")
            elif args.sync:
                # if the content is unchanged (and matches ipynb), we don't need
                # to update the timestamp as the contents manager will not throw in
                # that case (see the try/catch on read_pair(... must_match=True))
                log(f"[jupytext] Unchanged {shlex.quote(path)}")
            else:
                log(f"[jupytext] Updating the timestamp of {shlex.quote(path)}")
                os.utime(path, None)

        if args.pre_commit:
            system("git", "add", path)

        if args.pre_commit_mode and is_untracked(path):
            log(
                f"[jupytext] Error: the git index is outdated.\n"
                f"Please add the paired notebook with:\n"
                f"    git add {shlex.quote(path)}"
            )
            untracked_files += 1

        return

    if nb_dest:
        if nb_dest == nb_file and not dest_fmt:
            dest_fmt = fmt

        # Test consistency between dest name and output format
        if dest_fmt and nb_dest != "-":
            base_path(nb_dest, dest_fmt)

        # Describe what jupytext is doing
        if save_in_place:
            action = ""
        elif os.path.isfile(nb_dest) and args.update:
            if not nb_dest.endswith(".ipynb"):
                raise ValueError("--update is only for ipynb files")
            action = " (destination file updated)"
            check_file_version(notebook, nb_file, nb_dest)
            notebook = combine_inputs_with_outputs(notebook, read(nb_dest), fmt=fmt)
        elif os.path.isfile(nb_dest):
            suggest_update = (
                " [use --update to preserve cell outputs and ids]"
                if nb_dest.endswith(".ipynb")
                else ""
            )
            action = f" (destination file replaced{suggest_update})"
        else:
            action = ""

        formats = notebook.metadata.get("jupytext", {}).get("formats")
        formats = long_form_multiple_formats(formats)
        if formats:
            try:
                base_path_out, _ = find_base_path_and_format(nb_dest, formats)
            except InconsistentPath:
                # Drop 'formats' if the destination is not part of the paired notebooks
                formats = {}
                notebook.metadata.get("jupytext", {}).pop("formats")

        lazy_write(nb_dest, fmt=dest_fmt, action=action)

        nb_dest_in_pair = formats and any(
            os.path.exists(alt_path) and os.path.samefile(nb_dest, alt_path)
            for alt_path, _ in paired_paths(nb_file, fmt, formats)
        )

        if (
            nb_dest_in_pair
            and os.path.isfile(nb_file)
            and not nb_file.endswith(".ipynb")
            and os.path.isfile(nb_dest)
            and nb_dest.endswith(".ipynb")
        ):
            # If the destination is an ipynb file and is in the pair, then we
            # update the original text file timestamp, as required by our Content Manager
            # Otherwise Jupyter will refuse to open the paired notebook #335
            # NB: An alternative is --use-source-timestamp
            lazy_write(nb_file, update_timestamp_only=True)

    # c. Synchronize paired notebooks
    elif args.sync:
        write_pair(nb_file, formats, lazy_write)

    return untracked_files
</t>
<t tx="ekr.20241206101345.407">"""Read and write Jupyter notebooks as text files"""

import logging
import os
import sys
import warnings
from copy import copy, deepcopy

import nbformat
from nbformat.v4.nbbase import NotebookNode, new_code_cell, new_notebook
from nbformat.v4.rwbase import NotebookReader, NotebookWriter

from .cell_metadata import _IGNORE_CELL_METADATA
from .formats import (
    _VALID_FORMAT_OPTIONS,
    divine_format,
    format_name_for_ext,
    get_format_implementation,
    guess_format,
    long_form_one_format,
    read_format_from_metadata,
    rearrange_jupytext_metadata,
    update_jupytext_formats_metadata,
)
from .header import (
    encoding_and_executable,
    header_to_metadata_and_cell,
    insert_jupytext_info_and_filter_metadata,
    insert_or_test_version_number,
    metadata_and_cell_to_header,
)
from .languages import (
    default_language_from_metadata_and_ext,
    set_main_and_cell_language,
)
from .metadata_filter import filter_metadata, update_metadata_filters
from .myst import MYST_FORMAT_NAME, myst_extensions, myst_to_notebook, notebook_to_myst
from .pandoc import md_to_notebook, notebook_to_md
from .pep8 import pep8_lines_between_cells
from .quarto import notebook_to_qmd, qmd_to_notebook
from .version import __version__


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.408">class NotSupportedNBFormatVersion(NotImplementedError):
    """An error issued when the current notebook format is not supported by this version of Jupytext"""
</t>
<t tx="ekr.20241206101345.409">class TextNotebookConverter(NotebookReader, NotebookWriter):
    """A class that can read or write a Jupyter notebook as text"""
    @others
</t>
<t tx="ekr.20241206101345.41">def incorrectly_encoded_metadata(text):
    """Encode a text that Jupytext cannot parse as a cell metadata"""
    return {"incorrectly_encoded_metadata": text}
</t>
<t tx="ekr.20241206101345.410">def __init__(self, fmt, config):
    self.fmt = copy(long_form_one_format(fmt))
    self.config = config
    self.ext = self.fmt["extension"]
    self.implementation = get_format_implementation(
        self.ext, self.fmt.get("format_name")
    )
</t>
<t tx="ekr.20241206101345.411">def update_fmt_with_notebook_options(self, metadata, read=False):
    """Update format options with the values in the notebook metadata, and record those
    options in the notebook metadata"""
    # The settings in the Jupytext configuration file have precedence over the metadata in the notebook
    # when the notebook is saved. This is because the metadata in the notebook might not be visible
    # in the text representation when e.g. notebook_metadata_filter="-all", which makes them hard to edit.
    if not read and self.config is not None:
        self.config.set_default_format_options(self.fmt, read)

    # Use format options from the notebook if not already set by the config
    for opt in _VALID_FORMAT_OPTIONS:
        if opt in metadata.get("jupytext", {}):
            self.fmt.setdefault(opt, metadata["jupytext"][opt])

    # When we read the notebook we use the values of the config as defaults, as again the text representation
    # of the notebook might not store the format options when notebook_metadata_filter="-all"
    if read and self.config is not None:
        self.config.set_default_format_options(self.fmt, read=read)

    # We save the format options in the notebook metadata
    for opt in _VALID_FORMAT_OPTIONS:
        if opt in self.fmt:
            metadata.setdefault("jupytext", {})[opt] = self.fmt[opt]

    # Is this format the same as that documented in the YAML header? If so, we want to know the format version
    file_fmt = metadata.get("jupytext", {}).get("text_representation", {})
    if self.fmt.get("extension") == file_fmt.get("extension") and self.fmt.get(
        "format_name"
    ) == file_fmt.get("format_name"):
        self.fmt.update(file_fmt)

    # rST to md conversion should happen only once
    if metadata.get("jupytext", {}).get("rst2md") is True:
        metadata["jupytext"]["rst2md"] = False
</t>
<t tx="ekr.20241206101345.412">def reads(self, s, **_):
    """Read a notebook represented as text"""
    if self.fmt.get("format_name") == "pandoc":
        return md_to_notebook(s)

    if self.fmt.get("format_name") == "quarto":
        return qmd_to_notebook(s)

    if self.fmt.get("format_name") == MYST_FORMAT_NAME:
        return myst_to_notebook(s)

    lines = s.splitlines()

    cells = []
    metadata, jupyter_md, header_cell, pos = header_to_metadata_and_cell(
        lines,
        self.implementation.header_prefix,
        self.implementation.header_suffix,
        self.implementation.extension,
        self.fmt.get(
            "root_level_metadata_as_raw_cell",
            self.config.root_level_metadata_as_raw_cell
            if self.config is not None
            else True,
        ),
    )
    default_language = default_language_from_metadata_and_ext(
        metadata, self.implementation.extension
    )
    self.update_fmt_with_notebook_options(metadata, read=True)

    if header_cell:
        cells.append(header_cell)

    lines = lines[pos:]

    if (
        self.implementation.format_name
        and self.implementation.format_name.startswith("sphinx")
    ):
        cells.append(new_code_cell(source="%matplotlib inline"))

    cell_metadata_json = False

    while lines:
        reader = self.implementation.cell_reader_class(self.fmt, default_language)
        cell, pos = reader.read(lines)
        cells.append(cell)
        cell_metadata_json = cell_metadata_json or reader.cell_metadata_json
        if pos &lt;= 0:
            raise Exception(
                "Blocked at lines " + "\n".join(lines[:6])
            )  # pragma: no cover
        lines = lines[pos:]

    custom_cell_magics = self.fmt.get("custom_cell_magics", "").split(",")
    set_main_and_cell_language(
        metadata, cells, self.implementation.extension, custom_cell_magics
    )
    cell_metadata = set()
    for cell in cells:
        cell_metadata.update(cell.metadata.keys())
    update_metadata_filters(metadata, jupyter_md, cell_metadata)

    if cell_metadata_json:
        metadata.setdefault("jupytext", {}).setdefault("cell_metadata_json", True)

    if (
        self.implementation.format_name
        and self.implementation.format_name.startswith("sphinx")
    ):
        filtered_cells = []
        for i, cell in enumerate(cells):
            if (
                cell.source == ""
                and i &gt; 0
                and i + 1 &lt; len(cells)
                and cells[i - 1].cell_type != "markdown"
                and cells[i + 1].cell_type != "markdown"
            ):
                continue
            filtered_cells.append(cell)
        cells = filtered_cells

    return new_notebook(cells=cells, metadata=metadata)
</t>
<t tx="ekr.20241206101345.413">def filter_notebook(self, nb, metadata, preserve_cell_ids=False):
    self.update_fmt_with_notebook_options(nb.metadata)
    unsupported_keys = set()
    metadata = insert_jupytext_info_and_filter_metadata(
        metadata, self.fmt, self.implementation, unsupported_keys=unsupported_keys
    )

    cells = []
    for cell in nb.cells:
        cell_metadata = filter_metadata(
            cell.metadata,
            self.fmt.get("cell_metadata_filter"),
            _IGNORE_CELL_METADATA,
            unsupported_keys=unsupported_keys,
        )

        if preserve_cell_ids and hasattr(cell, "id"):
            id = {"id": cell.id}
        else:
            id = {}

        if cell.cell_type == "code":
            cells.append(
                new_code_cell(source=cell.source, metadata=cell_metadata, **id)
            )
        else:
            cells.append(
                NotebookNode(
                    source=cell.source,
                    metadata=cell_metadata,
                    cell_type=cell.cell_type,
                    **id,
                )
            )

    _warn_on_unsupported_keys(unsupported_keys)

    return NotebookNode(
        nbformat=nb.nbformat,
        nbformat_minor=nb.nbformat_minor,
        metadata=metadata,
        cells=cells,
    )
</t>
<t tx="ekr.20241206101345.414">def writes(self, nb, metadata=None, **kwargs):
    """Return the text representation of the notebook"""
    if self.fmt.get("format_name") == "pandoc":
        return notebook_to_md(
            self.filter_notebook(nb, metadata, preserve_cell_ids=True)
        )
    if self.fmt.get("format_name") == "quarto" or self.ext == ".qmd":
        return notebook_to_qmd(self.filter_notebook(nb, metadata))
    if self.fmt.get(
        "format_name"
    ) == MYST_FORMAT_NAME or self.ext in myst_extensions(no_md=True):
        pygments_lexer = metadata.get("language_info", {}).get(
            "pygments_lexer", None
        )
        return notebook_to_myst(
            self.filter_notebook(nb, metadata),
            default_lexer=pygments_lexer,
        )

    # Copy the notebook, in order to be sure we do not modify the original notebook
    nb = NotebookNode(
        nbformat=nb.nbformat,
        nbformat_minor=nb.nbformat_minor,
        metadata=deepcopy(metadata or nb.metadata),
        cells=nb.cells,
    )

    metadata = nb.metadata
    default_language = (
        default_language_from_metadata_and_ext(
            metadata, self.implementation.extension, True
        )
        or "python"
    )
    self.update_fmt_with_notebook_options(nb.metadata)
    if "use_runtools" not in self.fmt:
        for cell in nb.cells:
            if cell.metadata.get("hide_input", False) or cell.metadata.get(
                "hide_output", False
            ):
                self.fmt["use_runtools"] = True
                break

    header = encoding_and_executable(nb, metadata, self.ext)
    unsupported_keys = set()
    header_content, header_lines_to_next_cell = metadata_and_cell_to_header(
        nb,
        metadata,
        self.implementation,
        self.fmt,
        unsupported_keys=unsupported_keys,
    )
    header.extend(header_content)

    cell_exporters = []
    looking_for_first_markdown_cell = (
        self.implementation.format_name
        and self.implementation.format_name.startswith("sphinx")
    )
    split_at_heading = self.fmt.get("split_at_heading", False)

    for cell in nb.cells:
        if looking_for_first_markdown_cell and cell.cell_type == "markdown":
            cell.metadata.setdefault("cell_marker", '"""')
            looking_for_first_markdown_cell = False

        cell_exporters.append(
            self.implementation.cell_exporter_class(
                cell, default_language, self.fmt, unsupported_keys=unsupported_keys
            )
        )

    _warn_on_unsupported_keys(unsupported_keys)

    texts = [cell.cell_to_text() for cell in cell_exporters]
    lines = []

    # concatenate cells in reverse order to determine how many blank lines (pep8)
    for i, cell in reversed(list(enumerate(cell_exporters))):
        text = cell.remove_eoc_marker(texts[i], lines)

        if (
            i == 0
            and self.implementation.format_name
            and self.implementation.format_name.startswith("sphinx")
            and (text in [["%matplotlib inline"], ["# %matplotlib inline"]])
        ):
            continue

        lines_to_next_cell = cell.lines_to_next_cell
        if lines_to_next_cell is None:
            lines_to_next_cell = pep8_lines_between_cells(
                text, lines, self.implementation.extension
            )

        text.extend([""] * lines_to_next_cell)

        # two blank lines between markdown cells in Rmd when those do not have explicit region markers
        if self.ext in [".md", ".markdown", ".Rmd"] and not cell.is_code():
            if (
                i + 1 &lt; len(cell_exporters)
                and not cell_exporters[i + 1].is_code()
                and not texts[i][0].startswith("&lt;!-- #")
                and not texts[i + 1][0].startswith("&lt;!-- #")
                and (
                    not split_at_heading
                    or not (texts[i + 1] and texts[i + 1][0].startswith("#"))
                )
            ):
                text.append("")

        # "" between two consecutive code cells in sphinx
        if self.implementation.format_name.startswith("sphinx") and cell.is_code():
            if i + 1 &lt; len(cell_exporters) and cell_exporters[i + 1].is_code():
                text.append('""')

        lines = text + lines

    if header_lines_to_next_cell is None:
        header_lines_to_next_cell = pep8_lines_between_cells(
            header_content, lines, self.implementation.extension
        )

    header.extend([""] * header_lines_to_next_cell)

    return "\n".join(header + lines)
</t>
<t tx="ekr.20241206101345.415">def reads(text, fmt=None, as_version=nbformat.NO_CONVERT, config=None, **kwargs):
    """
    Read a notebook from a string

    :param text: the text representation of the notebook
    :param fmt: (optional) the jupytext format like `md`, `py:percent`, ...
    :param as_version: see nbformat.reads
    :param config: (optional) a Jupytext configuration object
    :param kwargs: (not used) additional parameters for nbformat.reads
    :return: the notebook
    """
    fmt = copy(fmt) if fmt else divine_format(text)
    fmt = long_form_one_format(fmt)
    ext = fmt["extension"]

    if ext == ".ipynb":
        nb = nbformat.reads(text, as_version, **kwargs)
        (version, version_minor) = nbformat.reader.get_version(nb)
        if version != 4:
            warnings.warn(
                f"Notebooks in nbformat version {version}.{version_minor} are not supported by Jupytext. "
                f"Please consider converting them to nbformat version 4.x with "
                f"'jupyter nbconvert --to notebook --inplace'"
            )
        return nb

    format_name = read_format_from_metadata(text, ext) or fmt.get("format_name")

    if format_name:
        format_options = {}
    else:
        format_name, format_options = guess_format(text, ext)

    if format_name:
        fmt["format_name"] = format_name

    fmt.update(format_options)
    reader = TextNotebookConverter(fmt, config)
    notebook = reader.reads(text, **kwargs)
    rearrange_jupytext_metadata(notebook.metadata)

    if format_name and insert_or_test_version_number():
        notebook.metadata.setdefault("jupytext", {}).setdefault(
            "text_representation", {}
        ).update({"extension": ext, "format_name": format_name})

    return notebook
</t>
<t tx="ekr.20241206101345.416">def read(fp, as_version=nbformat.NO_CONVERT, fmt=None, config=None, **kwargs):
    """Read a notebook from a file name or a file object

    :param fp: a file name or a file object
    :param as_version: see nbformat.read
    :param fmt: (optional) the jupytext format like `md`, `py:percent`, ...
    :param config: (optional) a Jupytext configuration object
    :param kwargs: (not used) additional parameters for nbformat.read
    :return: the notebook
    """
    if as_version != nbformat.NO_CONVERT and not isinstance(as_version, int):
        raise TypeError(
            "Second argument 'as_version' should be either nbformat.NO_CONVERT, or an integer."
        )

    if fp == "-":
        text = sys.stdin.read()
        # Update the input format by reference if missing
        if isinstance(fmt, dict) and not fmt:
            fmt.update(long_form_one_format(divine_format(text)))
        return reads(text, fmt)

    if not hasattr(fp, "read"):
        # Treat fp as a file name
        fp = str(fp)
        _, ext = os.path.splitext(fp)
        fmt = copy(fmt or {})
        if not isinstance(fmt, dict):
            fmt = long_form_one_format(fmt)
        fmt.update({"extension": ext})
        with open(fp, encoding="utf-8") as stream:
            return read(stream, as_version=as_version, fmt=fmt, config=config, **kwargs)

    if fmt is not None:
        fmt = long_form_one_format(fmt)
        if fmt["extension"] == ".ipynb":
            notebook = nbformat.read(fp, as_version, **kwargs)
            rearrange_jupytext_metadata(notebook.metadata)
            return notebook

    return reads(fp.read(), fmt, config=config, **kwargs)
</t>
<t tx="ekr.20241206101345.417">def writes(notebook, fmt, version=nbformat.NO_CONVERT, config=None, **kwargs):
    """Return the text representation of the notebook

    :param notebook: the notebook
    :param fmt: the jupytext format like `md`, `py:percent`, ...
    :param version: see nbformat.writes
    :param config: (optional) a Jupytext configuration object
    :param kwargs: (not used) additional parameters for nbformat.writes
    :return: the text representation of the notebook
    """
    if version is not nbformat.NO_CONVERT:
        if not isinstance(version, int):
            raise TypeError(
                "The argument 'version' should be either nbformat.NO_CONVERT, or an integer."
            )
        notebook = nbformat.convert(notebook, version)
    (version, version_minor) = nbformat.reader.get_version(notebook)
    if version &lt; 4:
        raise NotSupportedNBFormatVersion(
            f"Notebooks in nbformat version {version}.{version_minor} are not supported by Jupytext. "
            f"Please convert your notebooks to nbformat version 4 with "
            f"'jupyter nbconvert --to notebook --inplace', or call this function with 'version=4'."
        )
    if version &gt; 4 or (version == 4 and version_minor &gt; 5):
        warnings.warn(
            f"Notebooks in nbformat version {version}.{version_minor} "
            f"have not been tested with Jupytext version {__version__}."
        )

    metadata = deepcopy(notebook.metadata)
    rearrange_jupytext_metadata(metadata)
    fmt = copy(fmt)
    fmt = long_form_one_format(fmt, metadata)
    ext = fmt["extension"]
    format_name = fmt.get("format_name")

    if ext == ".ipynb":
        return nbformat.writes(
            drop_text_representation_metadata(notebook, metadata),
            version,
            **kwargs,
        )

    if not format_name:
        format_name = format_name_for_ext(metadata, ext, explicit_default=False)

    if format_name:
        fmt["format_name"] = format_name
        update_jupytext_formats_metadata(metadata, fmt)

    writer = TextNotebookConverter(fmt, config)
    return writer.writes(notebook, metadata)
</t>
<t tx="ekr.20241206101345.418">def drop_text_representation_metadata(notebook, metadata=None):
    """When the notebook is saved to an ipynb file, we drop the text_representation metadata"""
    if metadata is None:
        # Make a copy to avoid modification by reference
        metadata = deepcopy(notebook["metadata"])

    jupytext_metadata = metadata.get("jupytext", {})
    jupytext_metadata.pop("text_representation", {})

    # Remove the jupytext section if empty
    if not jupytext_metadata:
        metadata.pop("jupytext", {})

    return NotebookNode(
        nbformat=notebook["nbformat"],
        nbformat_minor=notebook["nbformat_minor"],
        metadata=metadata,
        cells=notebook["cells"],
    )
</t>
<t tx="ekr.20241206101345.419">def write(nb, fp, version=nbformat.NO_CONVERT, fmt=None, config=None, **kwargs):
    """Write a notebook to a file name or a file object

    :param nb: the notebook
    :param fp: a file name or a file object
    :param version: see nbformat.write
    :param fmt: (optional if fp is a file name) the jupytext format like `md`, `py:percent`, ...
    :param config: (optional) a Jupytext configuration object
    :param kwargs: (not used) additional parameters for nbformat.write
    """
    if fp == "-":
        # Use sys.stdout.buffer when possible, and explicit utf-8 encoding, cf. #331
        content = writes(nb, version=version, fmt=fmt, config=config, **kwargs)
        try:
            # Python 3
            sys.stdout.buffer.write(content.encode("utf-8"))
        except AttributeError:
            sys.stdout.write(content.encode("utf-8"))
        return

    if not hasattr(fp, "write"):
        # Treat fp as a file name
        fp = str(fp)
        _, ext = os.path.splitext(fp)
        fmt = copy(fmt or {})
        fmt = long_form_one_format(fmt, update={"extension": ext})
        create_prefix_dir(fp, fmt)

        with open(fp, "w", encoding="utf-8") as stream:
            write(nb, stream, version=version, fmt=fmt, config=config, **kwargs)
            return
    else:
        assert (
            fmt is not None
        ), "'fmt' argument in jupytext.write is mandatory unless fp is a file name"

    content = writes(nb, version=version, fmt=fmt, config=config, **kwargs)
    if isinstance(content, bytes):
        content = content.decode("utf8")
    fp.write(content)
    if not content.endswith("\n"):
        fp.write("\n")
</t>
<t tx="ekr.20241206101345.42">def is_identifier(text):
    return bool(_IS_IDENTIFIER.match(text))
</t>
<t tx="ekr.20241206101345.420">def create_prefix_dir(nb_file, fmt):
    """Create directory if fmt has a prefix"""
    if "prefix" in fmt:
        nb_dir = os.path.dirname(nb_file) + os.path.sep
        if not os.path.isdir(nb_dir):
            logging.log(
                logging.WARNING, "[jupytext] creating missing directory %s", nb_dir
            )
            os.makedirs(nb_dir)
</t>
<t tx="ekr.20241206101345.421">def _warn_on_unsupported_keys(unsupported_keys):
    if unsupported_keys:
        warnings.warn(
            f"The following metadata cannot be exported "
            f"to the text notebook: {sorted(unsupported_keys)}"
        )
</t>
<t tx="ekr.20241206101345.422">import pdb

if 1:
    # print contents of jupytext's config file.
    g.app.jupytextManager.get_jupytext_config_file()
if 0:
    &lt;&lt; Use cli: --to fmt ekr-small-test.ipynb &gt;&gt;
if 0:
    # Use API.
    g.cls()
    # Imports from jupytext.py
    # import nbformat
    # from nbformat.v4.nbbase import NotebookNode, new_code_cell, new_notebook
    # EKR: really nbformat.notebooknode.NotebookNode
    # from nbformat.v4.rwbase import NotebookReader, NotebookWriter
    import jupytext

    py_file = r'c:\test\ekr-small-test.py'
    ipynb_file = r'c:\test\ekr-small-test.ipynb'

    # read a notebook (class nbformat.notebooknode.NotebookNode) from a file
    nb = jupytext.read(ipynb_file, fmt='py:percent')
    # keys are 'cells', 'metadata', 'nbformat', 'nbformat_minor'.
    if 0:
        for z in nb:
            print(f"{z:&gt;20} {nb[z]}")
    if 1:
        print('metadata...')
        d = nb['metadata']
        for z in d:
            print(f"{z}: {g.objToString(d[z])}")
    if 1:
        print('')
        print('cells...')
        for i, cell in enumerate(nb['cells']):
            print(f"cell {i}: {g.objToString(cell)}")

    # write a notebook to a file in the 'py:percent' format
    ### jupytext.write(nb, "notebook.py", fmt="py:percent")
</t>
<t tx="ekr.20241206101345.423">from jupytext.cli import jupytext as cli
kind = 'py'  # 'py' or 'auto' ('auto' prevents 'percent').
fmt = 'percent'  # 'percent' or 'light'
args = ['--to', f"{kind}:{fmt}", r'c:\test\ekr-small-test.ipynb']
if 0:
    # b 6 @509 jupytext_single_file.
    # b 7 @411
    pdb.runcall(cli, args)
else:
    cli(args)
</t>
<t tx="ekr.20241206101345.424"></t>
<t tx="ekr.20241206101345.425">@language python
# %%
# Leo created this prefix from @data jupyter-prefix.
# Jupyter's 'New Python Text Notebook with Percent Format` created this file.

# ---
# jupyter:
#   jupytext:
#     formats: py:percent
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %%

</t>
<t tx="ekr.20241206101345.426"></t>
<t tx="ekr.20241206101345.427">@language python
# %%

# ---
# jupyter:
#   jupytext:
#     formats: ipynb,py:percent
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %% [markdown] editable=true slideshow={"slide_type": ""}
# # Creating a new notebook

# %% [markdown]
# 1. Open the command palette with the shortcut: `Ctrl/Command` + `Shift` + `P`
# 2. Search for the command `Create New Blank Notebook`

# %% editable=true slideshow={"slide_type": ""}
# How to get back to the start page

# %% editable=true slideshow={"slide_type": ""}
1. Open the command palette with the shortcut: `Ctrl/Command` + `Shift` + `P`

2. Search for the command `Python: Open Start Page`

# %% [markdown] editable=true slideshow={"slide_type": ""}
# # Getting started

# %% [markdown]
# You are currently viewing what we call our Notebook Editor. It is an interactive document based on Jupyter Notebooks that supports the intermixing of code, outputs and markdown documentation. 
#
# This cell is a markdown cell. To edit the text in this cell, simply double click on the cell to change it into edit mode.
#
# The next cell below is a code cell. You can switch a cell between code and markdown by clicking on the code ![code icon](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/codeIcon.PNG) /markdown ![markdown icon](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/markdownIcon.PNG) icons or using the keyboard shortcut `M` and `Y` respectively.

# %%
print("hello world")

# %% [markdown]
# * To execute the code in the cell above, click on the cell to select it and then either press the play ![play](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/playIcon.PNG) button in the cell toolbar, or use the keyboard shortcut `Ctrl/Command` + `Enter`.
# * To edit the code, just click in cell and start editing.
# * To add a new cell below, click the `Add Cell` icon ![add cell](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/addIcon.PNG) at the bottom left of the cell or enter command mode with the `ESC` Key and then use the keyboard shortcut `B` to create the new cell below.
#

# %% [markdown]
# # Features

# %% [markdown]
# **Variable explorer**
#
# To view all your active variables and their current values in the notebook, click on the variable explorer icon ![variable explorer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/variableExplorerIcon.PNG) in the top toolbar.
#
# ![Variable Explorer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/variableexplorer.png)
#
# **Data Viewer**
#
# To view your data frame in a more visual "Excel" like format, open the variable explorer and to the left of any dataframe object, you will see the data viewer icon ![data viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/dataViewerIcon.PNG) which you can click to open the data viewer.
#
# ![Data Viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/dataviewer.gif)
#
# **Convert to Python File**
#
# To export your notebook to a Python file (.py), click on the `Convert to Python script` icon ![Export icon](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/exportIcon.PNG) in the top toolbar 
#
# ![Export](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/savetopythonfile.png)
#
# **Plot Viewer**
#
# If you have a graph (such as matplotlib) in your output, you'll notice if you hover over the graph, the `Plot Viewer` icon ![Plot Viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/plotViewerIcon.PNG) will appear in the top left. Click the icon to open up the graph in the Plotviewer which allows you to zoom on your plots and export it in formats such as png and jpeg.
#
# ![Plot Viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/plotviewer.gif)
#
# **Switching Kernels**
#
# The notebook editor will detect all kernels in your system by default. To change your notebook kernel, click on the kernel status in the top toolbar at the far right. For example, your kernel status may say "Python 3: Idle". This will open up the kernel selector where you can choose your desired kernel.
#
# ![Switching Kernels](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/kernelchange.gif)
#
# **Remote Jupyter Server**
#
# To connect to a remote Jupyter server, open the command prompt and search for the command `Specify remote or local Jupyter server for connections`. Then select `Existing` and enter the remote Jupyter server URL. Afterwards, you'll be prompted to reload the window and the Notebook will be opened connected to the remote Jupyter server.
#
# ![Remote](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/remoteserver.gif)

# %% [markdown] colab_type="text" id="-Rh3-Vt9Nev9"
# # More Resources

# %% [markdown]
# - [Data science tutorial for Visual Studio Code](https://code.visualstudio.com/docs/python/data-science-tutorial)
# - [Jupyter Notebooks in Visual Studio Code documentation](https://code.visualstudio.com/docs/python/jupyter-support)
</t>
<t tx="ekr.20241206101345.428">@language python
# %%
# A leading (misleading?) comment.
&lt;&lt; preamble &gt;&gt;
@others
@language json
</t>
<t tx="ekr.20241206101345.429"># %%
# ---
# jupyter:
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

</t>
<t tx="ekr.20241206101345.43">def is_valid_metadata_key(text):
    """Can this text be a proper key?"""
    return bool(_IS_VALID_METADATA_KEY.match(text))
</t>
<t tx="ekr.20241206101345.430">@language python
</t>
<t tx="ekr.20241206101345.431"># %%
2 + 666 + 4
</t>
<t tx="ekr.20241206101345.432"># %%
print('hi changed externally')
</t>
<t tx="ekr.20241206101345.433"># %% [markdown]
# This is a markdown cell

# %% [markdown]
# Another markdown cell

# %%
</t>
<t tx="ekr.20241206101345.434">&lt;&lt; prefix &gt;&gt;
@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.435"># ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

</t>
<t tx="ekr.20241206101345.436"># %% [markdown]
# Loading and Analyzing Data
# ==========================

</t>
<t tx="ekr.20241206101345.437"># %%
import pandas as pd  # programmers like shortening names for things, so they usually import pandas as "pd"
import altair as alt # again with the shortening of names
import requests # we use it for downloading the data so we don't have to save it on GitHub (too big!)
import zipfile # for de-compressing the files we download from the EPA

</t>
<t tx="ekr.20241206101345.438"># %% [markdown]
# ## Load the air quality data
# The EPA published PM2.5 daily summary files annually [on their website](https://aqs.epa.gov/aqsweb/airdata/download_files.html#Daily). This data is the "PM2.5 FRM/FEM Mass (88101)" dataset.
# Pandas understands what a CSV file is, so here we can just load them into two `DataFrame`s. A data frame is simply one representation of a table of data. It is the most important form of storage for working with data in pandas.

</t>
<t tx="ekr.20241206101345.439"># %%
# Download the data from the EPA website
data_file_urls = [
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2020.zip',
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2019.zip'
]
# copied this example from https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests
for url in data_file_urls:
    local_filename = "data/{}".format(url.split('/')[-1])
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192): 
                f.write(chunk)
# and unzip the files
files_to_unzip = ["data/{}".format(url.split('/')[-1]) for url in data_file_urls]
for f in files_to_unzip:
    with zipfile.ZipFile(f,"r") as zip_ref:
        zip_ref.extractall("data")

</t>
<t tx="ekr.20241206101345.44">def is_jupyter_language(language):
    """Is this a jupyter language?"""
    for lang in _JUPYTER_LANGUAGES:
        if language.lower() == lang.lower():
            return True
    return False
</t>
<t tx="ekr.20241206101345.440"># %%
air_2019_df = pd.read_csv("data/daily_88101_2019.csv")
air_2020_df = pd.read_csv("data/daily_88101_2020.csv")
air_2020_df.head() # this helpfully prints out the first few rows with headers to preview the data

</t>
<t tx="ekr.20241206101345.441"># %%
"{} rows for 2019, {} rows for 2020".format(air_2019_df.shape[0], air_2019_df.shape[0])

</t>
<t tx="ekr.20241206101345.442"># %% [markdown]
# ## Aggregate and average MA data by city
# Let's compare MA data by city in 2020 and 2019.

</t>
<t tx="ekr.20241206101345.443"># %%
# Step 1 - filter the data down by state name

is_MA_data = air_2020_df['State Name'] == "Massachusetts"
air_MA_2020_df = air_2020_df[is_MA_data]

is_MA_data = air_2019_df['State Name'] == "Massachusetts"
air_MA_2019_df = air_2019_df[is_MA_data]

"{} reports for MA in 2019, {} reports for MA in 2020".format(air_MA_2019_df.shape[0], air_MA_2020_df.shape[0])

</t>
<t tx="ekr.20241206101345.444"># %%
# now trim down to just the columns we care about so it is easier to understand
interesting_columns = ['City Name', 'Latitude', 'Longitude', 'Arithmetic Mean']
air_MA_2020_df = pd.DataFrame(air_MA_2020_df, columns=interesting_columns)
air_MA_2019_df = pd.DataFrame(air_MA_2019_df, columns=interesting_columns)
air_MA_2019_df

</t>
<t tx="ekr.20241206101345.445"># %%
# now group all the records by city and average them
avg_by_city_2020_MA_df = air_MA_2020_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
avg_by_city_2019_MA_df = air_MA_2019_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
# now we need to add in a year column so we can tell the data apart!
avg_by_city_2020_MA_df['year'] = 2020
avg_by_city_2019_MA_df['year'] = 2019
# now we can just contacetane the two dataframes to get all our data in one place
ma_city_avg_df = avg_by_city_2019_MA_df.append(avg_by_city_2020_MA_df)
ma_city_avg_df.to_csv('data/MA-city-year-avg.csv')
ma_city_avg_df

</t>
<t tx="ekr.20241206101345.446"># %% [markdown]
# ## Visually Inspect the Data

</t>
<t tx="ekr.20241206101345.447"># %%
alt.Chart(ma_city_avg_df, height=300).mark_bar().encode(
    alt.X('year:N'),
    alt.Y('Mean'),
    color='year:N',
    column=alt.Column(field='City', type='ordinal', spacing=10)
).properties( 
    title="MA City Average PM2.5 (by year)",
)
</t>
<t tx="ekr.20241206101345.448">@language jupytext
&lt;&lt; prefix &gt;&gt;
@others
@tabwidth -4
</t>
<t tx="ekr.20241206101345.449">@language jupytext
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# %%
</t>
<t tx="ekr.20241206101345.45">def parse_key_equal_value(text):
    """Parse a string of the form 'key1=value1 key2=value2'"""
    # Empty metadata?
    text = text.strip()
    if not text:
        return {}

    last_space_pos = text.rfind(" ")

    # Just an identifier?
    if not text.startswith("--") and is_identifier(text[last_space_pos + 1 :]):
        key = text[last_space_pos + 1 :]
        value = None
        result = {key: value}
        if last_space_pos &gt; 0:
            result.update(parse_key_equal_value(text[:last_space_pos]))
        return result

    # Iterate on the '=' signs, starting from the right
    equal_sign_pos = None
    while True:
        equal_sign_pos = text.rfind("=", None, equal_sign_pos)
        if equal_sign_pos &lt; 0:
            return incorrectly_encoded_metadata(text)

        # Do we have an identifier on the left of the equal sign?
        prev_whitespace = text[:equal_sign_pos].rstrip().rfind(" ")
        key = text[prev_whitespace + 1 : equal_sign_pos].strip()
        if not is_valid_metadata_key(key):
            continue

        try:
            value = relax_json_loads(text[equal_sign_pos + 1 :])
        except (ValueError, SyntaxError):
            # try with a longer expression
            continue

        # Combine with remaining metadata
        metadata = (
            parse_key_equal_value(text[:prev_whitespace]) if prev_whitespace &gt; 0 else {}
        )

        # Append our value
        metadata[key] = value

        # And return
        return metadata
</t>
<t tx="ekr.20241206101345.450">@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

</t>
<t tx="ekr.20241206101345.451"># %%
import pandas as pd  # programmers like shortening names for things, so they usually import pandas as "pd"
import altair as alt # again with the shortening of names
import requests # we use it for downloading the data so we don't have to save it on GitHub (too big!)
import zipfile # for de-compressing the files we download from the EPA

</t>
<t tx="ekr.20241206101345.452"># %% [markdown]
# ## Load the air quality data
# The EPA published PM2.5 daily summary files annually [on their website](https://aqs.epa.gov/aqsweb/airdata/download_files.html#Daily). This data is the "PM2.5 FRM/FEM Mass (88101)" dataset.
# Pandas understands what a CSV file is, so here we can just load them into two `DataFrame`s. A data frame is simply one representation of a table of data. It is the most important form of storage for working with data in pandas.

</t>
<t tx="ekr.20241206101345.453"># %%
# Download the data from the EPA website
data_file_urls = [
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2020.zip',
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2019.zip'
]
# copied this example from https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests
for url in data_file_urls:
    local_filename = "data/{}".format(url.split('/')[-1])
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192): 
                f.write(chunk)
# and unzip the files
files_to_unzip = ["data/{}".format(url.split('/')[-1]) for url in data_file_urls]
for f in files_to_unzip:
    with zipfile.ZipFile(f,"r") as zip_ref:
        zip_ref.extractall("data")

</t>
<t tx="ekr.20241206101345.454"># %%
air_2019_df = pd.read_csv("data/daily_88101_2019.csv")
air_2020_df = pd.read_csv("data/daily_88101_2020.csv")
air_2020_df.head() # this helpfully prints out the first few rows with headers to preview the data

</t>
<t tx="ekr.20241206101345.455"># %%
"{} rows for 2019, {} rows for 2020".format(air_2019_df.shape[0], air_2019_df.shape[0])

</t>
<t tx="ekr.20241206101345.456"># %% [markdown]
# ## Aggregate and average MA data by city
# Let's compare MA data by city in 2020 and 2019.

</t>
<t tx="ekr.20241206101345.457"># %%
# Step 1 - filter the data down by state name

is_MA_data = air_2020_df['State Name'] == "Massachusetts"
air_MA_2020_df = air_2020_df[is_MA_data]

is_MA_data = air_2019_df['State Name'] == "Massachusetts"
air_MA_2019_df = air_2019_df[is_MA_data]

"{} reports for MA in 2019, {} reports for MA in 2020".format(air_MA_2019_df.shape[0], air_MA_2020_df.shape[0])

</t>
<t tx="ekr.20241206101345.458"># %%
# now trim down to just the columns we care about so it is easier to understand
interesting_columns = ['City Name', 'Latitude', 'Longitude', 'Arithmetic Mean']
air_MA_2020_df = pd.DataFrame(air_MA_2020_df, columns=interesting_columns)
air_MA_2019_df = pd.DataFrame(air_MA_2019_df, columns=interesting_columns)
air_MA_2019_df

</t>
<t tx="ekr.20241206101345.459"># %%
# now group all the records by city and average them
avg_by_city_2020_MA_df = air_MA_2020_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
avg_by_city_2019_MA_df = air_MA_2019_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
# now we need to add in a year column so we can tell the data apart!
avg_by_city_2020_MA_df['year'] = 2020
avg_by_city_2019_MA_df['year'] = 2019
# now we can just contacetane the two dataframes to get all our data in one place
ma_city_avg_df = avg_by_city_2019_MA_df.append(avg_by_city_2020_MA_df)
ma_city_avg_df.to_csv('data/MA-city-year-avg.csv')
ma_city_avg_df

</t>
<t tx="ekr.20241206101345.46">def relax_json_loads(text, catch=False):
    """Parse a JSON string or similar"""
    text = text.strip()
    try:
        return loads(text)
    except JSONDecodeError:
        pass

    if not catch:
        return ast.literal_eval(text)

    try:
        return ast.literal_eval(text)
    except (ValueError, SyntaxError):
        pass

    return incorrectly_encoded_metadata(text)
</t>
<t tx="ekr.20241206101345.460"># %% [markdown]
# ## Visually Inspect the Data

</t>
<t tx="ekr.20241206101345.461"># %%
alt.Chart(ma_city_avg_df, height=300).mark_bar().encode(
    alt.X('year:N'),
    alt.Y('Mean'),
    color='year:N',
    column=alt.Column(field='City', type='ordinal', spacing=10)
).properties( 
    title="MA City Average PM2.5 (by year)",
)
</t>
<t tx="ekr.20241206101345.462"># jm = g.app.jupytextManager
import io
import jupytext
path = r'c:/test/example-data-analysis.ipynb'
last = c.lastTopLevel()
p = last.insertAfter()
p.h = f"Test: {path}"
# The guts of jm.read.
fmt = 'py:percent'
notebook = jupytext.read(path, fmt=fmt)
with io.StringIO() as f:
    jupytext.write(notebook, f, fmt=fmt)
    contents = f.getvalue()
    p.b = contents
c.redraw(p)
</t>
<t tx="ekr.20241206101345.463">@language matlab

clear all, close all, clc

A=imread('../DATA/dog.jpg');
X=double(rgb2gray(A)); % Convert RBG-&gt;gray, 256 bit-&gt;double.
nx = size(X,1); ny = size(X,2);
imagesc(X), axis off, colormap gray 

[U,S,V] = svd(X);

for r=[5 20 100];  % Truncation value
    Xapprox = U(:,1:r)*S(1:r,1:r)*V(:,1:r)'; % Approx. image
    figure, imagesc(Xapprox), axis off
    title(['r=',num2str(r,'%d'),']);
end

%% f_ch01_ex02_2
subplot(1,2,1), semilogy(diag(S),'k') 
subplot(1,2,2), plot(cumsum(diag(S))/sum(diag(S)),'k') 
</t>
<t tx="ekr.20241206101345.464">&lt;&lt; prefix &gt;&gt;
@others
@language jupytext
@tabwidth -4
</t>
<t tx="ekr.20241206101345.465"># ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

</t>
<t tx="ekr.20241206101345.466"># %%

from matplotlib.image import imread
import matplotlib.pyplot as plt
import numpy as np
import os
</t>
<t tx="ekr.20241206101345.467"># %% [markdown]
#
# Section 1
# ---------
#
# Section 1, line 1.

# %%

plt.rcParams['figure.figsize'] = [16, 8]

A = imread(os.path.join('..','DATA','dog.jpg'))
X = np.mean(A, -1); # Convert RGB to grayscale

img = plt.imshow(X)
img.set_cmap('gray')
plt.axis('off')
plt.show()
</t>
<t tx="ekr.20241206101345.468"># %%

U, S, VT = np.linalg.svd(X,full_matrices=False)
S = np.diag(S)

j = 0
for r in (5, 20, 100):
    # Construct approximate image
    Xapprox = U[:,:r] @ S[0:r,:r] @ VT[:r,:]
    plt.figure(j+1)
    j += 1
    img = plt.imshow(Xapprox)
    img.set_cmap('gray')
    plt.axis('off')
    plt.title('r = ' + str(r))
    plt.show()

</t>
<t tx="ekr.20241206101345.469"># %%

plt.figure(1)
plt.semilogy(np.diag(S))
plt.title('Singular Values')
plt.show()

plt.figure(2)
plt.plot(np.cumsum(np.diag(S))/np.sum(np.diag(S)))
plt.title('Singular Values: Cumulative Sum')
plt.show()

</t>
<t tx="ekr.20241206101345.47">def is_json_metadata(text):
    """Is this a JSON metadata?"""
    first_curly_bracket = text.find("{")
    if first_curly_bracket &lt; 0:
        return False

    first_equal_sign = text.find("=")
    if first_equal_sign &lt; 0:
        return True

    return first_curly_bracket &lt; first_equal_sign
</t>
<t tx="ekr.20241206101345.470"></t>
<t tx="ekr.20241206101345.471">@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

A **bold** word.

# %%
import pandas as pd  # A comment

def spam(s: str) -&gt; str:
    print(s)
    
spam('Hi')

    
# %% [markdown]

*Italics* and **bold**.
</t>
<t tx="ekr.20241206101345.472">@language rest
@wrap
@nosearch

https://github.com/leo-editor/leo-editor/issues/4146
PR: https://github.com/leo-editor/leo-editor/pull/4147

- Item 1
- Item 2.

***A label.

.. comment.

@language python

def spam(s: str) -&gt; str:
    g.es_print(s)
    
spam('Hi')</t>
<t tx="ekr.20241206101345.474">@language glsl

if 1 {

}</t>
<t tx="ekr.20241206101345.475">@language python
def spam(s: str) -&gt; str:
    """A docstring a"""
    g.es_print(s)

spam('1234')</t>
<t tx="ekr.20241206101345.476">@language javascript

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;
</t>
<t tx="ekr.20241206101345.477">@language html

&lt;head&gt;

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;

&lt;/head&gt;</t>
<t tx="ekr.20241206101345.478">@language html

&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;

&lt;h2&gt;Width and Height Attributes&lt;/h2&gt;

&lt;p&gt;
The width and height attributes of the img tag, defines the width and
height of the image:
&lt;/p&gt;

&lt;img src="img_girl.jpg" width="500" height="600"&gt;

&lt;/body&gt;
&lt;/html&gt;
</t>
<t tx="ekr.20241206101345.479">@language javascript

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;

@language vue

&lt;template&gt;
  &lt;p class="greeting"&gt;{{ greeting }}&lt;/p&gt;
&lt;/template&gt;

@language css

&lt;style&gt;
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
.greeting {
  color: red;
  font-weight: bold;
}
&lt;/style&gt;</t>
<t tx="ekr.20241206101345.48">def text_to_metadata(text, allow_title=False):
    """Parse the language/cell title and associated metadata"""
    # Parse the language or cell title = everything before the last blank space before { or =
    text = text.strip()
    first_curly_bracket = text.find("{")
    first_equal_sign = text.find("=")

    if first_curly_bracket &lt; 0 or (0 &lt;= first_equal_sign &lt; first_curly_bracket):
        # this is a key=value metadata line
        # case one = the options may be preceded with a language
        if not allow_title:
            if is_jupyter_language(text):
                return text, {}
            if " " not in text:
                return "", parse_key_equal_value(text)
            language, options = text.split(" ", 1)
            if is_jupyter_language(language):
                return language, parse_key_equal_value(options)
            return "", parse_key_equal_value(text)

        # case two = a title may be before the options
        # we split the title into words
        if first_equal_sign &gt;= 0:
            words = text[:first_equal_sign].split(" ")
            # last word is the key before the equal sign!
            while words and not words[-1]:
                words.pop()
            if words:
                words.pop()
        else:
            words = text.split(" ")

        # and we remove words on the right that are attributes (they start with '.')
        while words and (not words[-1].strip() or words[-1].startswith(".")):
            words.pop()

        title = " ".join(words)
        return title, parse_key_equal_value(text[len(title) :])

    # json metadata line
    return (
        text[:first_curly_bracket].strip(),
        relax_json_loads(text[first_curly_bracket:], catch=True),
    )
</t>
<t tx="ekr.20241206101345.480">@language vue

&lt;template&gt;
  &lt;body&gt;
    &lt;h2&gt;Width and Height Attributes&lt;/h2&gt;
    &lt;p class="greeting"&gt;{{ greeting }}&lt;/p&gt;
    &lt;p&gt;
    The width and height attributes of the img tag, defines the width and
    height of the image:
    &lt;/p&gt;
    &lt;img src="img_girl.jpg" width="500" height="600"&gt;
  &lt;/body&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;

&lt;style&gt;
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
.greeting {
  color: red;
  font-weight: bold;
}
&lt;/style&gt;

&lt;template&gt;
  &lt;p class="greeting"&gt;{{ greeting 3 }}&lt;/p&gt;
&lt;/template&gt;</t>
<t tx="ekr.20241206101345.481">@language html

&lt;!DOCTYPE html&gt;
&lt;
&lt;body&gt;

&lt;/html&gt;
</t>
<t tx="ekr.20241206101345.482">@language css

&lt;style&gt;
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
&lt;/style&gt;

</t>
<t tx="ekr.20241206101345.49">def metadata_to_text(language_or_title, metadata=None, plain_json=False):
    """Write the cell metadata in the format key=value"""
    # Was metadata the first argument?
    if metadata is None:
        metadata, language_or_title = language_or_title, metadata

    metadata = {
        key: metadata[key] for key in metadata if key not in _JUPYTEXT_CELL_METADATA
    }
    text = [language_or_title] if language_or_title else []
    if language_or_title is None:
        if (
            "title" in metadata
            and "{" not in metadata["title"]
            and "=" not in metadata["title"]
        ):
            text.append(metadata.pop("title"))

    if plain_json:
        if metadata:
            text.append(dumps(metadata))
    else:
        for key in metadata:
            if key == "incorrectly_encoded_metadata":
                text.append(metadata[key])
            elif metadata[key] is None:
                text.append(key)
            else:
                text.append(f"{key}={dumps(metadata[key])}")
    return " ".join(text)
</t>
<t tx="ekr.20241206101345.491">import leo.core.leoGlobals as g
from openai import OpenAI
# client = OpenAI()

# Set your OpenAI API key
client = OpenAI(api_key = "&lt;YOUR KEY HERE&gt;")

@others

g.plugin_signon(__name__)
add_openai_command(c)

@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.492">def summarize_and_reformat(content):
     """
     Sends the given content to OpenAI to summarize and reformat it using the updated API.
     """
     try:
         # OpenAI API request
         response = client.chat.completions.create(
             model="gpt-4o-mini",  # Use "gpt-3.5-turbo" or "gpt-4" based on your access
             messages=[
                 {"role": "system", "content": 
                     "You are a helpful assistant. Summarize and reformat the text you are given."
                },
                 {"role": "user", "content": content},
             ],
             temperature=0.7,
             max_tokens=2000,  # Adjust as needed
         )
         # Extract and return the response
         message = response.choices[0].message.content
         g.es(f"Response from openai: {message}")
         return message
     except Exception as e:
         g.es(f"Error while interacting with OpenAI: {e}", color="red")
         return None
</t>
<t tx="ekr.20241206101345.493">def process_node_with_openai(c, p):
     """
     Summarizes and reformats the content of the current node and creates a new sub-node with the result.
     """
     # Get the current node's content
     content = p.b.strip()
     if not content:
         g.es("The node is empty. Nothing to summarize.", color="red")
         return

     # g.es(f"(content in the pane: {content}")
     g.es("Sending content to OpenAI for summarization and reformatting...", color="blue")
     # Call the OpenAI function
     summarized_content = summarize_and_reformat(content)

     if summarized_content:
         # Create a new sub-node with the summarized content
         new_node = p.insertAsLastChild()
         new_node.h = "Summarized and Reformatted Content"
         new_node.b = summarized_content
         g.es("New sub-node created with summarized and reformatted text.", color="green")
     else:
         g.es("Failed to create sub-node. See error logs for details.", color="red")
</t>
<t tx="ekr.20241206101345.494">def add_openai_command(c):
    """Add the command to the Leo editor."""
     command_name = 'summarize-and-reformat'
     c.k.registerCommand(command_name, lambda event: process_node_with_openai(c, c.p))
     g.es(f"Command '{command_name}' added to Leo.", color="green")
</t>
<t tx="ekr.20241206101345.5">@language csharp

// Test

if (abc):
    a = b;</t>
<t tx="ekr.20241206101345.50">"""Read notebook cells from their text representation"""

import re
import warnings
from copy import copy

from nbformat.v4.nbbase import new_code_cell, new_markdown_cell, new_raw_cell
from packaging.version import parse

from .doxygen import doxygen_to_markdown
from .languages import _SCRIPT_EXTENSIONS

# Sphinx Gallery is an optional dependency. And we intercept the SyntaxError for #301
try:
    from sphinx_gallery import __version__ as sg_version

    if parse(sg_version) &lt;= parse("0.7.0"):
        from sphinx_gallery.notebook import rst2md
    else:
        warnings.warn(
            f"Sphinx Gallery in version {sg_version} is not supported by Jupytext. "
            f"Please use sphinx-gallery&lt;=0.7.0 instead. "
            f"If that is an issue, feel free to report it "
            f"at https://github.com/mwouts/jupytext/issues, or even better, "
            f"prepare a PR to handle the new signature of "
            f"sphinx_gallery.notebook.rst2md."
        )
        rst2md = None
except (ImportError, SyntaxError):  # pragma: no cover
    rst2md = None

from .cell_metadata import (
    is_active,
    is_json_metadata,
    rmd_options_to_metadata,
    text_to_metadata,
)
from .languages import _JUPYTER_LANGUAGES_LOWER_AND_UPPER
from .magics import is_magic, need_explicit_marker, uncomment_magic, unescape_code_start
from .pep8 import pep8_lines_between_cells
from .stringparser import StringParser

_BLANK_LINE = re.compile(r"^\s*$")
_PY_INDENTED = re.compile(r"^\s")


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.51">def uncomment(lines, prefix="#", suffix=""):
    """Remove prefix and space, or only prefix, when possible"""
    if prefix:
        prefix_and_space = prefix + " "
        length_prefix = len(prefix)
        length_prefix_and_space = len(prefix_and_space)
        lines = [
            line[length_prefix_and_space:]
            if line.startswith(prefix_and_space)
            else (line[length_prefix:] if line.startswith(prefix) else line)
            for line in lines
        ]

    if suffix:
        space_and_suffix = " " + suffix
        length_suffix = len(suffix)
        length_space_and_suffix = len(space_and_suffix)
        lines = [
            line[:-length_space_and_suffix]
            if line.endswith(space_and_suffix)
            else (line[:-length_suffix] if line.endswith(suffix) else line)
            for line in lines
        ]

    return lines
</t>
<t tx="ekr.20241206101345.52">def paragraph_is_fully_commented(lines, comment, main_language):
    """Is the paragraph fully commented?"""
    for i, line in enumerate(lines):
        if line.startswith(comment):
            if line[len(comment) :].lstrip().startswith(comment):
                continue
            if is_magic(line, main_language):
                return False
            continue
        return i &gt; 0 and _BLANK_LINE.match(line)
    return True
</t>
<t tx="ekr.20241206101345.53">def next_code_is_indented(lines):
    """Is the next unescaped line indented?"""
    for line in lines:
        if _BLANK_LINE.match(line):
            continue
        return _PY_INDENTED.match(line)
    return False
</t>
<t tx="ekr.20241206101345.54">def count_lines_to_next_cell(cell_end_marker, next_cell_start, total, explicit_eoc):
    """How many blank lines between end of cell marker and next cell?"""
    if cell_end_marker &lt; total:
        lines_to_next_cell = next_cell_start - cell_end_marker
        if explicit_eoc:
            lines_to_next_cell -= 1
        if next_cell_start &gt;= total:
            lines_to_next_cell += 1
        return lines_to_next_cell

    return 1
</t>
<t tx="ekr.20241206101345.55">def last_two_lines_blank(source):
    """Are the two last lines blank, and not the third last one?"""
    if len(source) &lt; 3:
        return False
    return (
        not _BLANK_LINE.match(source[-3])
        and _BLANK_LINE.match(source[-2])
        and _BLANK_LINE.match(source[-1])
    )
</t>
<t tx="ekr.20241206101345.56">class BaseCellReader:
    """A class that can read notebook cells from their text representation"""
    @others
</t>
<t tx="ekr.20241206101345.57">default_comment_magics = None
lines_to_next_cell = 1

start_code_re = None
simple_start_code_re = None
end_code_re = None

# How to make code inactive
comment = ""

# Any specific prefix for lines in markdown cells (like in R spin format?)
markdown_prefix = None

def __init__(self, fmt=None, default_language=None):
    """Create a cell reader with empty content"""
    if not fmt:
        fmt = {}
    self.ext = fmt.get("extension")
    self.default_language = default_language or _SCRIPT_EXTENSIONS.get(
        self.ext, {}
    ).get("language", "python")
    self.comment_magics = fmt.get("comment_magics", self.default_comment_magics)
    self.use_runtools = fmt.get("use_runtools", False)
    self.format_version = fmt.get("format_version")
    self.metadata = None
    self.org_content = []
    self.content = []
    self.explicit_soc = None
    self.explicit_eoc = None
    self.cell_type = None
    self.language = None
    self.cell_metadata_json = fmt.get("cell_metadata_json", False)
    self.doxygen_equation_markers = fmt.get("doxygen_equation_markers", False)
</t>
<t tx="ekr.20241206101345.58">def read(self, lines):
    """Read one cell from the given lines, and return the cell,
    plus the position of the next cell
    """

    # Do we have an explicit code marker on the first line?
    self.metadata_and_language_from_option_line(lines[0])

    if self.metadata and "language" in self.metadata:
        self.language = self.metadata.pop("language")

    # Parse cell till its end and set content, lines_to_next_cell
    pos_next_cell = self.find_cell_content(lines)

    if self.cell_type == "code":
        new_cell = new_code_cell
    elif self.cell_type == "markdown":
        new_cell = new_markdown_cell
    else:
        new_cell = new_raw_cell

    if not self.metadata:
        self.metadata = {}

    if self.ext == ".py":
        expected_blank_lines = pep8_lines_between_cells(
            self.org_content or [""], lines[pos_next_cell:], self.ext
        )
    else:
        expected_blank_lines = 1

    if self.lines_to_next_cell != expected_blank_lines:
        self.metadata["lines_to_next_cell"] = self.lines_to_next_cell

    if self.language:
        self.metadata["language"] = self.language

    return (
        new_cell(source="\n".join(self.content), metadata=self.metadata),
        pos_next_cell,
    )
</t>
<t tx="ekr.20241206101345.59">def metadata_and_language_from_option_line(self, line):
    """Parse code options on the given line. When a start of a code cell
    is found, self.metadata is set to a dictionary."""
    if self.start_code_re.match(line):
        self.language, self.metadata = self.options_to_metadata(
            self.start_code_re.findall(line)[0]
        )
</t>
<t tx="ekr.20241206101345.6">http://www.google.com/search</t>
<t tx="ekr.20241206101345.60">def options_to_metadata(self, options):
    """Return language (str) and metadata (dict) from the option line"""
    raise NotImplementedError("Option parsing must be implemented in a sub class")
</t>
<t tx="ekr.20241206101345.61">def find_cell_end(self, lines):
    """Return position of end of cell marker, and position
    of first line after cell"""
    raise NotImplementedError("This method must be implemented in a sub class")
</t>
<t tx="ekr.20241206101345.62">def find_cell_content(self, lines):
    """Parse cell till its end and set content, lines_to_next_cell.
    Return the position of next cell start"""
    cell_end_marker, next_cell_start, self.explicit_eoc = self.find_cell_end(lines)

    # Metadata to dict
    if self.metadata is None:
        cell_start = 0
        self.metadata = {}
    else:
        cell_start = 1

    # Cell content
    source = lines[cell_start:cell_end_marker]
    self.org_content = copy(source)

    # Exactly two empty lines at the end of cell (caused by PEP8)?
    if self.ext == ".py" and self.explicit_eoc:
        if last_two_lines_blank(source):
            source = source[:-2]
            lines_to_end_of_cell_marker = 2
        else:
            lines_to_end_of_cell_marker = 0

        pep8_lines = pep8_lines_between_cells(
            source, lines[cell_end_marker:], self.ext
        )
        if lines_to_end_of_cell_marker != (0 if pep8_lines == 1 else 2):
            self.metadata[
                "lines_to_end_of_cell_marker"
            ] = lines_to_end_of_cell_marker

    # Uncomment content
    self.explicit_soc = cell_start &gt; 0
    self.content = self.extract_content(source)

    # Is this an inactive cell?
    if self.cell_type == "code":
        if not is_active(".ipynb", self.metadata):
            if self.metadata.get("active") == "":
                del self.metadata["active"]
            self.cell_type = "raw"
        elif self.ext in [".md", ".markdown"] and not self.language:
            # Markdown files in version &gt;= 1.3 represent code chunks with no language as Markdown cells
            if self.format_version not in ["1.0", "1.1"]:
                self.cell_type = "markdown"
                self.explicit_eoc = False
                cell_end_marker += 1
                self.content = lines[:cell_end_marker]
            # Previous versions mapped those to raw cells
            else:
                self.cell_type = "raw"

    # Explicit end of cell marker?
    if (
        next_cell_start + 1 &lt; len(lines)
        and _BLANK_LINE.match(lines[next_cell_start])
        and not _BLANK_LINE.match(lines[next_cell_start + 1])
    ):
        next_cell_start += 1
    elif (
        self.explicit_eoc
        and next_cell_start + 2 &lt; len(lines)
        and _BLANK_LINE.match(lines[next_cell_start])
        and _BLANK_LINE.match(lines[next_cell_start + 1])
        and not _BLANK_LINE.match(lines[next_cell_start + 2])
    ):
        next_cell_start += 2

    self.lines_to_next_cell = count_lines_to_next_cell(
        cell_end_marker, next_cell_start, len(lines), self.explicit_eoc
    )

    return next_cell_start
</t>
<t tx="ekr.20241206101345.63">def uncomment_code_and_magics(self, lines):
    """Uncomment code and possibly commented magic commands"""
    raise NotImplementedError("This method must be implemented in a sub class")
</t>
<t tx="ekr.20241206101345.64">def extract_content(self, lines):
    # Code cells with just a multiline string become Markdown cells
    if self.ext == ".py" and not is_active(
        self.ext, self.metadata, self.cell_type == "code"
    ):
        content = "\n".join(lines).strip()
        for prefix in [""] if self.ext != ".py" else ["", "r", "R"]:
            for triple_quote in ['"""', "'''"]:
                left = prefix + triple_quote
                right = triple_quote
                if (
                    content.startswith(left)
                    and content.endswith(right)
                    and len(content) &gt;= len(left + right)
                ):
                    content = content[len(left) : -len(right)]
                    # Trim first/last line return
                    if content.startswith("\n"):
                        content = content[1:]
                        left = left + "\n"
                    if content.endswith("\n"):
                        content = content[:-1]
                        right = "\n" + right

                    if not prefix:
                        if len(left) == len(right) == 4:
                            self.metadata["cell_marker"] = left[:3]
                    elif len(left[1:]) == len(right) == 4:
                        self.metadata["cell_marker"] = left[:4]
                    else:
                        self.metadata["cell_marker"] = left + "," + right
                    return content.splitlines()

    if not is_active(self.ext, self.metadata) or (
        "active" not in self.metadata
        and self.language
        and self.language != self.default_language
    ):
        return uncomment(
            lines, self.comment if self.ext not in [".r", ".R"] else "#"
        )

    return self.uncomment_code_and_magics(lines)
</t>
<t tx="ekr.20241206101345.65">class MarkdownCellReader(BaseCellReader):
    """Read notebook cells from Markdown documents"""
    @others
</t>
<t tx="ekr.20241206101345.66">comment = ""
start_code_re = re.compile(
    r"^```(`*)(\s*)({})($|\s.*$)".format(
        "|".join(_JUPYTER_LANGUAGES_LOWER_AND_UPPER).replace("+", "\\+")
    )
)
non_jupyter_code_re = re.compile(r"^```")
end_code_re = re.compile(r"^```\s*$")
start_region_re = re.compile(r"^&lt;!--\s*#(region|markdown|md|raw)(.*)--&gt;\s*$")
end_region_re = None
default_comment_magics = False

def __init__(self, fmt=None, default_language=None):
    super().__init__(fmt, default_language)
    self.split_at_heading = (fmt or {}).get("split_at_heading", False)
    self.in_region = False
    self.in_raw = False
    if self.format_version in ["1.0", "1.1"] and self.ext != ".Rmd":
        # Restore the pattern used in Markdown &lt;= 1.1
        self.start_code_re = re.compile(r"^```(.*)")
        self.non_jupyter_code_re = re.compile(r"^```\{")
</t>
<t tx="ekr.20241206101345.67">def metadata_and_language_from_option_line(self, line):
    match_region = self.start_region_re.match(line)
    if match_region:
        self.in_region = True
        groups = match_region.groups()
        region_name = groups[0]
        self.end_region_re = re.compile(rf"^&lt;!--\s*#end{region_name}\s*--&gt;\s*$")
        self.cell_metadata_json = self.cell_metadata_json or is_json_metadata(
            groups[1]
        )
        title, self.metadata = text_to_metadata(groups[1], allow_title=True)
        if region_name == "raw":
            self.cell_type = "raw"
        else:
            self.cell_type = "markdown"
        if title:
            self.metadata["title"] = title
        if region_name in ["markdown", "md"]:
            self.metadata["region_name"] = region_name
    elif self.start_code_re.match(line):
        self.language, self.metadata = self.options_to_metadata(
            self.start_code_re.findall(line)[0]
        )
        # Cells with a .noeval attribute are markdown cells #347
        if self.metadata.get(".noeval", "") is None:
            self.cell_type = "markdown"
            self.metadata = {}
            self.language = None
</t>
<t tx="ekr.20241206101345.68">def options_to_metadata(self, options):
    if isinstance(options, tuple):
        self.end_code_re = re.compile("```" + options[0])
        options = " ".join(options[1:])
    else:
        self.end_code_re = re.compile(r"^```\s*$")
    self.cell_metadata_json = self.cell_metadata_json or is_json_metadata(options)
    return text_to_metadata(options)
</t>
<t tx="ekr.20241206101345.69">def find_cell_end(self, lines):
    """Return position of end of cell marker, and position
    of first line after cell"""
    if self.in_region:
        for i, line in enumerate(lines):
            if self.end_region_re.match(line):
                return i, i + 1, True
    elif self.metadata is None:
        # default markdown: (last) two consecutive blank lines, except when in code blocks
        self.cell_type = "markdown"
        prev_blank = 0
        in_explicit_code_block = False
        in_indented_code_block = False

        for i, line in enumerate(lines):
            if in_explicit_code_block and self.end_code_re.match(line):
                in_explicit_code_block = False
                continue

            if (
                prev_blank
                and line.startswith("    ")
                and not _BLANK_LINE.match(line)
            ):
                in_indented_code_block = True
                prev_blank = 0
                continue

            if (
                in_indented_code_block
                and not _BLANK_LINE.match(line)
                and not line.startswith("    ")
            ):
                in_indented_code_block = False

            if in_indented_code_block or in_explicit_code_block:
                continue

            if self.start_region_re.match(line):
                if i &gt; 1 and prev_blank:
                    return i - 1, i, False
                return i, i, False

            if self.start_code_re.match(line):
                if line.startswith("```{bibliography}"):
                    in_explicit_code_block = True
                    prev_blank = 0
                    continue

                # Cells with a .noeval attribute are markdown cells #347
                _, metadata = self.options_to_metadata(
                    self.start_code_re.findall(line)[0]
                )
                if metadata.get(".noeval", "") is None:
                    in_explicit_code_block = True
                    prev_blank = 0
                    continue

                if i &gt; 1 and prev_blank:
                    return i - 1, i, False
                return i, i, False

            if self.non_jupyter_code_re.match(line):
                if prev_blank &gt;= 2:
                    return i - 2, i, True
                in_explicit_code_block = True
                prev_blank = 0
                continue

            if self.split_at_heading and line.startswith("#") and prev_blank &gt;= 1:
                return i - 1, i, False

            if _BLANK_LINE.match(lines[i]):
                prev_blank += 1
            elif prev_blank &gt;= 2:
                return i - 2, i, True
            else:
                prev_blank = 0
    else:
        self.cell_type = "code"
        # At some point we could remove the below, in which we make sure not to break language strings
        # into multiple cells (#419). Indeed, now that the markdown cell uses one extra backtick (#712)
        # we should not have the issue any more
        parser = StringParser(self.language or self.default_language)
        for i, line in enumerate(lines):
            # skip cell header
            if i == 0:
                continue

            if parser.is_quoted():
                parser.read_line(line)
                continue

            parser.read_line(line)
            if self.end_code_re.match(line):
                return i, i + 1, True

    # End not found
    return len(lines), len(lines), False
</t>
<t tx="ekr.20241206101345.7"></t>
<t tx="ekr.20241206101345.70">def uncomment_code_and_magics(self, lines):
    if self.cell_type == "code" and self.comment_magics:
        lines = uncomment_magic(lines, self.language)
    if self.cell_type == "markdown" and self.doxygen_equation_markers:
        lines = doxygen_to_markdown("\n".join(lines)).splitlines()
    return lines
</t>
<t tx="ekr.20241206101345.71">class RMarkdownCellReader(MarkdownCellReader):
    """Read notebook cells from R Markdown notebooks"""
    @others
</t>
<t tx="ekr.20241206101345.72">comment = ""
start_code_re = re.compile(r"^```{(.*)}\s*$")
non_jupyter_code_re = re.compile(r"^```([^\{]|\s*$)")
default_language = "R"
default_comment_magics = True

def options_to_metadata(self, options):
    return rmd_options_to_metadata(options, self.use_runtools)
</t>
<t tx="ekr.20241206101345.73">def uncomment_code_and_magics(self, lines):
    if (
        self.cell_type == "code"
        and self.comment_magics
        and is_active(self.ext, self.metadata)
    ):
        uncomment_magic(lines, self.language or self.default_language)

    return lines
</t>
<t tx="ekr.20241206101345.74">class ScriptCellReader(BaseCellReader):  # pylint: disable=W0223
    """Read notebook cells from scripts
    (common base for R and Python scripts)"""
    @others
</t>
<t tx="ekr.20241206101345.75">def uncomment_code_and_magics(self, lines):
    if self.cell_type == "code" or self.comment != "#'":
        if self.comment_magics:
            if is_active(self.ext, self.metadata):
                uncomment_magic(
                    lines,
                    self.language or self.default_language,
                    explicitly_code=self.explicit_soc,
                )
                if (
                    self.cell_type == "code"
                    and not self.explicit_soc
                    and need_explicit_marker(
                        lines, self.language or self.default_language
                    )
                ):
                    self.metadata["comment_questions"] = False
            else:
                lines = uncomment(lines)

    if self.default_language == "go" and self.language is None:
        lines = [
            re.sub(r"^((//\s*)*)(//\s*gonb:%%)", r"\1%%", line) for line in lines
        ]

    if self.cell_type == "code":
        return unescape_code_start(
            lines, self.ext, self.language or self.default_language
        )

    return uncomment(
        lines, self.markdown_prefix or self.comment, self.comment_suffix
    )
</t>
<t tx="ekr.20241206101345.76">class RScriptCellReader(ScriptCellReader):
    """Read notebook cells from R scripts written according
    to the knitr-spin syntax"""
    @others
</t>
<t tx="ekr.20241206101345.77">comment = "#'"
comment_suffix = ""
markdown_prefix = "#'"
default_language = "R"
start_code_re = re.compile(r"^#\+(.*)\s*$")
default_comment_magics = True

def options_to_metadata(self, options):
    return rmd_options_to_metadata("r " + options, self.use_runtools)
</t>
<t tx="ekr.20241206101345.78">def find_cell_end(self, lines):
    """Return position of end of cell marker, and position
    of first line after cell"""
    if self.metadata is None and lines[0].startswith("#'"):
        self.cell_type = "markdown"
        for i, line in enumerate(lines):
            if not line.startswith("#'"):
                if _BLANK_LINE.match(line):
                    return i, i + 1, False
                return i, i, False

        return len(lines), len(lines), False

    if self.metadata and "cell_type" in self.metadata:
        self.cell_type = self.metadata.pop("cell_type")
    else:
        self.cell_type = "code"

    parser = StringParser(self.language or self.default_language)
    for i, line in enumerate(lines):
        # skip cell header
        if self.metadata is not None and i == 0:
            continue

        if parser.is_quoted():
            parser.read_line(line)
            continue

        parser.read_line(line)

        if self.start_code_re.match(line) or (
            self.markdown_prefix and line.startswith(self.markdown_prefix)
        ):
            if i &gt; 0 and _BLANK_LINE.match(lines[i - 1]):
                if i &gt; 1 and _BLANK_LINE.match(lines[i - 2]):
                    return i - 2, i, False
                return i - 1, i, False
            return i, i, False

        if _BLANK_LINE.match(line):
            if not next_code_is_indented(lines[i:]):
                if i &gt; 0:
                    return i, i + 1, False
                if len(lines) &gt; 1 and not _BLANK_LINE.match(lines[1]):
                    return 1, 1, False
                return 1, 2, False

    return len(lines), len(lines), False
</t>
<t tx="ekr.20241206101345.79">class LightScriptCellReader(ScriptCellReader):
    """Read notebook cells from plain Python or Julia files. Cells
    are identified by line breaks, unless they start with an
    explicit marker '# +'"""
    @others
</t>
<t tx="ekr.20241206101345.80">default_comment_magics = True
cell_marker_start = None
cell_marker_end = None

def __init__(self, fmt=None, default_language=None):
    super().__init__(fmt, default_language)
    self.ext = self.ext or ".py"
    script = _SCRIPT_EXTENSIONS[self.ext]
    self.default_language = default_language or script["language"]
    self.comment = script["comment"]
    self.comment_suffix = script.get("comment_suffix", "")
    self.ignore_end_marker = True
    self.explicit_end_marker_required = False
    if (
        fmt
        and fmt.get("format_name", "light") == "light"
        and "cell_markers" in fmt
        and fmt["cell_markers"] != "+,-"
    ):
        self.cell_marker_start, self.cell_marker_end = fmt["cell_markers"].split(
            ",", 1
        )
        self.start_code_re = re.compile(
            "^"
            + re.escape(self.comment)
            + r"\s*"
            + self.cell_marker_start
            + r"(.*)$"
        )
        self.end_code_re = re.compile(
            "^" + re.escape(self.comment) + r"\s*" + self.cell_marker_end + r"\s*$"
        )
    else:
        self.start_code_re = re.compile(
            "^" + re.escape(self.comment) + r"\s*\+(.*)$"
        )
</t>
<t tx="ekr.20241206101345.81">def metadata_and_language_from_option_line(self, line):
    if self.start_code_re.match(line):
        # Remove the OCAML suffix
        if self.comment_suffix:
            if line.endswith(" " + self.comment_suffix):
                line = line[: -len(" " + self.comment_suffix)]
            elif line.endswith(self.comment_suffix):
                line = line[: -len(self.comment_suffix)]

        # We want to parse inner most regions as cells.
        # Thus, if we find another region start before the end for this region,
        # we will have ignore the metadata that we found here, and move on to the next cell.
        groups = self.start_code_re.match(line).groups()
        self.language, self.metadata = self.options_to_metadata(groups[0])
        self.ignore_end_marker = False
        if self.cell_marker_start:
            self.explicit_end_marker_required = True
    elif self.simple_start_code_re and self.simple_start_code_re.match(line):
        self.metadata = {}
        self.ignore_end_marker = False
    elif self.cell_marker_end and self.end_code_re.match(line):
        self.metadata = None
        self.cell_type = "code"
</t>
<t tx="ekr.20241206101345.82">def options_to_metadata(self, options):
    self.cell_metadata_json = self.cell_metadata_json or is_json_metadata(options)
    title, metadata = text_to_metadata(options, allow_title=True)

    # Cell type
    for cell_type in ["markdown", "raw", "md"]:
        code = f"[{cell_type}]"
        if code in title:
            title = title.replace(code, "").strip()
            metadata["cell_type"] = cell_type
            if cell_type == "md":
                metadata["region_name"] = cell_type
                metadata["cell_type"] = "markdown"
            break

    # Spyder has sub cells
    cell_depth = 0
    while title.startswith("%"):
        cell_depth += 1
        title = title[1:]

    if cell_depth:
        metadata["cell_depth"] = cell_depth
        title = title.strip()

    if title:
        metadata["title"] = title

    return None, metadata
</t>
<t tx="ekr.20241206101345.83">def find_cell_end(self, lines):
    """Return position of end of cell marker, and position of first line after cell"""
    if (
        self.metadata is None
        and not (self.cell_marker_end and self.end_code_re.match(lines[0]))
        and paragraph_is_fully_commented(lines, self.comment, self.default_language)
    ):
        self.cell_type = "markdown"
        for i, line in enumerate(lines):
            if _BLANK_LINE.match(line):
                return i, i + 1, False
        return len(lines), len(lines), False

    if self.metadata is None:
        self.end_code_re = None
    elif not self.cell_marker_end:
        end_of_cell = self.metadata.get("endofcell", "-")
        self.end_code_re = re.compile(
            "^" + re.escape(self.comment) + " " + end_of_cell + r"\s*$"
        )

    return self.find_region_end(lines)
</t>
<t tx="ekr.20241206101345.84">def find_region_end(self, lines):
    """Find the end of the region started with start and end markers"""
    if self.metadata and "cell_type" in self.metadata:
        self.cell_type = self.metadata.pop("cell_type")
    else:
        self.cell_type = "code"

    parser = StringParser(self.language or self.default_language)
    for i, line in enumerate(lines):
        # skip cell header
        if self.metadata is not None and i == 0:
            continue

        if parser.is_quoted():
            parser.read_line(line)
            continue

        parser.read_line(line)
        # New code region
        # Simple code pattern in LightScripts must be preceded with a blank line
        if self.start_code_re.match(line) or (
            self.simple_start_code_re
            and self.simple_start_code_re.match(line)
            and (
                self.cell_marker_start or i == 0 or _BLANK_LINE.match(lines[i - 1])
            )
        ):
            if self.explicit_end_marker_required:
                # Metadata here was conditioned on finding an explicit end marker
                # before the next start marker. So we dismiss it.
                self.metadata = None
                self.language = None

            if i &gt; 0 and _BLANK_LINE.match(lines[i - 1]):
                if i &gt; 1 and _BLANK_LINE.match(lines[i - 2]):
                    return i - 2, i, False
                return i - 1, i, False
            return i, i, False

        if not self.ignore_end_marker and self.end_code_re:
            if self.end_code_re.match(line):
                return i, i + 1, True
        elif _BLANK_LINE.match(line):
            if not next_code_is_indented(lines[i:]):
                if i &gt; 0:
                    return i, i + 1, False
                if len(lines) &gt; 1 and not _BLANK_LINE.match(lines[1]):
                    return 1, 1, False
                return 1, 2, False

    return len(lines), len(lines), False
</t>
<t tx="ekr.20241206101345.85">class DoublePercentScriptCellReader(LightScriptCellReader):
    """Read notebook cells from Spyder/VScode scripts (#59)"""
    @others
</t>
<t tx="ekr.20241206101345.86">default_comment_magics = True

def __init__(self, fmt, default_language=None):
    LightScriptCellReader.__init__(self, fmt, default_language)
    script = _SCRIPT_EXTENSIONS[self.ext]
    self.default_language = default_language or script["language"]
    self.comment = script["comment"]
    self.comment_suffix = script.get("comment_suffix", "")
    self.start_code_re = re.compile(
        rf"^\s*{re.escape(self.comment)}\s*%%(%*)\s(.*)$"
    )
    self.alternative_start_code_re = re.compile(
        r"^\s*{}\s*(%%|&lt;codecell&gt;|In\[[0-9 ]*\]:?)\s*$".format(
            re.escape(self.comment)
        )
    )
    self.explicit_soc = True
</t>
<t tx="ekr.20241206101345.87">def metadata_and_language_from_option_line(self, line):
    """Parse code options on the given line. When a start of a code cell
    is found, self.metadata is set to a dictionary."""
    if self.start_code_re.match(line):
        line = uncomment([line], self.comment, self.comment_suffix)[0]
        self.language, self.metadata = self.options_to_metadata(
            line[line.find("%%") + 2 :]
        )
    else:
        self.metadata = {}
</t>
<t tx="ekr.20241206101345.88">def find_cell_content(self, lines):
    """Parse cell till its end and set content, lines_to_next_cell.
    Return the position of next cell start"""
    cell_end_marker, next_cell_start, explicit_eoc = self.find_cell_end(lines)

    # Metadata to dict
    if self.start_code_re.match(lines[0]) or self.alternative_start_code_re.match(
        lines[0]
    ):
        cell_start = 1
    else:
        cell_start = 0

    # Cell content
    source = lines[cell_start:cell_end_marker]
    self.org_content = copy(source)
    self.content = self.extract_content(source)

    self.lines_to_next_cell = count_lines_to_next_cell(
        cell_end_marker, next_cell_start, len(lines), explicit_eoc
    )

    return next_cell_start
</t>
<t tx="ekr.20241206101345.89">def find_cell_end(self, lines):
    """Return position of end of cell marker, and position
    of first line after cell"""

    if self.metadata and "cell_type" in self.metadata:
        self.cell_type = self.metadata.pop("cell_type")
    elif not is_active(".ipynb", self.metadata):
        if self.metadata.get("active") == "":
            del self.metadata["active"]
        self.cell_type = "raw"
        self.comment = ""
    else:
        self.cell_type = "code"

    next_cell = len(lines)
    parser = StringParser(self.language or self.default_language)
    for i, line in enumerate(lines):
        if parser.is_quoted():
            parser.read_line(line)
            continue

        parser.read_line(line)
        if i &gt; 0 and (
            self.start_code_re.match(line)
            or self.alternative_start_code_re.match(line)
        ):
            next_cell = i
            break

    if last_two_lines_blank(lines[:next_cell]):
        return next_cell - 2, next_cell, False
    if next_cell &gt; 0 and _BLANK_LINE.match(lines[next_cell - 1]):
        return next_cell - 1, next_cell, False
    return next_cell, next_cell, False
</t>
<t tx="ekr.20241206101345.9">@language md

# Loading and Analyzing Data
# ==========================

*Italics* and **bold**.
More markdown. nxy

Here is come python code::

    print('Hi')
</t>
<t tx="ekr.20241206101345.90">class HydrogenCellReader(DoublePercentScriptCellReader):
    """Read notebook cells from Hydrogen scripts (#59)"""

    default_comment_magics = False
</t>
<t tx="ekr.20241206101345.91">class SphinxGalleryScriptCellReader(ScriptCellReader):  # pylint: disable=W0223
    """Read notebook cells from Sphinx Gallery scripts (#80)"""
    @others
</t>
<t tx="ekr.20241206101345.92">comment = "#"
default_language = "python"
default_comment_magics = True
twenty_hash = re.compile(r"^#( |)#{19,}\s*$")
default_markdown_cell_marker = "#" * 79
markdown_marker = None

def __init__(self, fmt=None, default_language="python"):
    super().__init__(fmt, default_language)
    self.ext = ".py"
    self.rst2md = (fmt or {}).get("rst2md", False)
</t>
<t tx="ekr.20241206101345.93">def start_of_new_markdown_cell(self, line):
    """Does this line starts a new markdown cell?
    Then, return the cell marker"""
    for empty_markdown_cell in ['""', "''"]:
        if line == empty_markdown_cell:
            return empty_markdown_cell

    for triple_quote in ['"""', "'''"]:
        if line.startswith(triple_quote):
            return triple_quote

    if self.twenty_hash.match(line):
        return line

    return None
</t>
<t tx="ekr.20241206101345.94">def metadata_and_language_from_option_line(self, line):
    self.markdown_marker = self.start_of_new_markdown_cell(line)
    if self.markdown_marker:
        self.cell_type = "markdown"
        if self.markdown_marker != self.default_markdown_cell_marker:
            self.metadata = {"cell_marker": self.markdown_marker}
    else:
        self.cell_type = "code"
</t>
<t tx="ekr.20241206101345.95">def find_cell_end(self, lines):
    """Return position of end of cell, and position
    of first line after cell, and whether there was an
    explicit end of cell marker"""

    if self.cell_type == "markdown":
        # Empty cell "" or ''
        if len(self.markdown_marker) &lt;= 2:
            if len(lines) == 1 or _BLANK_LINE.match(lines[1]):
                return 0, 2, True
            return 0, 1, True

        # Multi-line comment with triple quote
        if len(self.markdown_marker) == 3:
            for i, line in enumerate(lines):
                if (
                    i &gt; 0 or line.strip() != self.markdown_marker
                ) and line.rstrip().endswith(self.markdown_marker):
                    explicit_end_of_cell_marker = (
                        line.strip() == self.markdown_marker
                    )
                    if explicit_end_of_cell_marker:
                        end_of_cell = i
                    else:
                        end_of_cell = i + 1
                    if len(lines) &lt;= i + 1 or _BLANK_LINE.match(lines[i + 1]):
                        return end_of_cell, i + 2, explicit_end_of_cell_marker
                    return end_of_cell, i + 1, explicit_end_of_cell_marker
        else:
            # 20 # or more
            for i, line in enumerate(lines[1:], 1):
                if not line.startswith(self.comment):
                    if _BLANK_LINE.match(line):
                        return i, i + 1, False
                    return i, i, False

    elif self.cell_type == "code":
        parser = StringParser("python")
        for i, line in enumerate(lines):
            if parser.is_quoted():
                parser.read_line(line)
                continue

            if self.start_of_new_markdown_cell(line):
                if i &gt; 0 and _BLANK_LINE.match(lines[i - 1]):
                    return i - 1, i, False
                return i, i, False
            parser.read_line(line)

    return len(lines), len(lines), False
</t>
<t tx="ekr.20241206101345.96">def find_cell_content(self, lines):
    """Parse cell till its end and set content, lines_to_next_cell.
    Return the position of next cell start"""
    cell_end_marker, next_cell_start, explicit_eoc = self.find_cell_end(lines)

    # Metadata to dict
    cell_start = 0
    if self.cell_type == "markdown":
        if self.markdown_marker in ['"""', "'''"]:
            # Remove the triple quotes
            if lines[0].strip() == self.markdown_marker:
                cell_start = 1
            else:
                lines[0] = lines[0][3:]
            if not explicit_eoc:
                last = lines[cell_end_marker - 1]
                lines[cell_end_marker - 1] = last[
                    : last.rfind(self.markdown_marker)
                ]
        if self.twenty_hash.match(self.markdown_marker):
            cell_start = 1
    else:
        self.metadata = {}

    # Cell content
    source = lines[cell_start:cell_end_marker]
    self.org_content = copy(source)

    if self.cell_type == "code" and self.comment_magics:
        uncomment_magic(source, self.language or self.default_language)

    if self.cell_type == "markdown" and source:
        if self.markdown_marker.startswith(self.comment):
            source = uncomment(source, self.comment)
        if self.rst2md:
            if rst2md:
                source = rst2md("\n".join(source)).splitlines()
            else:
                raise ImportError(
                    "Could not import rst2md from sphinx_gallery.notebook"
                )  # pragma: no cover

    self.content = source

    self.lines_to_next_cell = count_lines_to_next_cell(
        cell_end_marker, next_cell_start, len(lines), explicit_eoc
    )

    return next_cell_start
</t>
<t tx="ekr.20241206101345.97">"""Export notebook cells as text"""

import re
import warnings
from copy import copy

from .cell_metadata import (
    _IGNORE_CELL_METADATA,
    is_active,
    metadata_to_double_percent_options,
    metadata_to_rmd_options,
    metadata_to_text,
)
from .cell_reader import LightScriptCellReader, MarkdownCellReader, RMarkdownCellReader
from .doxygen import markdown_to_doxygen
from .languages import _SCRIPT_EXTENSIONS, cell_language, comment_lines, same_language
from .magics import comment_magic, escape_code_start, need_explicit_marker
from .metadata_filter import filter_metadata
from .pep8 import pep8_lines_between_cells


@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20241206101345.98">def cell_source(cell):
    """Return the source of the current cell, as an array of lines"""
    source = cell.source
    if source == "":
        return [""]
    if source.endswith("\n"):
        return source.splitlines() + [""]
    return source.splitlines()
</t>
<t tx="ekr.20241206101345.99">def three_backticks_or_more(lines):
    """Return a string with enough backticks to encapsulate the given code cell in Markdown
    cf. https://github.com/mwouts/jupytext/issues/712"""
    code_cell_delimiter = "```"
    for line in lines:
        if not line.startswith(code_cell_delimiter):
            continue
        for char in line[len(code_cell_delimiter) :]:
            if char != "`":
                break
            code_cell_delimiter += "`"
        code_cell_delimiter += "`"

    return code_cell_delimiter
</t>
<t tx="ekr.20251214045245.1"></t>
<t tx="ekr.20251214045245.10">@language python
# https://peps.python.org/pep-0750/
try:
    from string.templatelib import Template
    name = 'Edward'
    template: Template = t"Hello {name}"
except AttributeError:
    pass
</t>
<t tx="ekr.20251214045245.100">&lt;&lt; prefix &gt;&gt;
@others
@language jupytext
@tabwidth -4
</t>
<t tx="ekr.20251214045245.101"># ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

</t>
<t tx="ekr.20251214045245.102"># %%

from matplotlib.image import imread
import matplotlib.pyplot as plt
import numpy as np
import os
</t>
<t tx="ekr.20251214045245.103"># %% [markdown]
#
# Section 1
# ---------
#
# Section 1, line 1.

# %%

plt.rcParams['figure.figsize'] = [16, 8]

A = imread(os.path.join('..','DATA','dog.jpg'))
X = np.mean(A, -1); # Convert RGB to grayscale

img = plt.imshow(X)
img.set_cmap('gray')
plt.axis('off')
plt.show()
</t>
<t tx="ekr.20251214045245.104"># %%

U, S, VT = np.linalg.svd(X,full_matrices=False)
S = np.diag(S)

j = 0
for r in (5, 20, 100):
    # Construct approximate image
    Xapprox = U[:,:r] @ S[0:r,:r] @ VT[:r,:]
    plt.figure(j+1)
    j += 1
    img = plt.imshow(Xapprox)
    img.set_cmap('gray')
    plt.axis('off')
    plt.title('r = ' + str(r))
    plt.show()

</t>
<t tx="ekr.20251214045245.105"># %%

plt.figure(1)
plt.semilogy(np.diag(S))
plt.title('Singular Values')
plt.show()

plt.figure(2)
plt.plot(np.cumsum(np.diag(S))/np.sum(np.diag(S)))
plt.title('Singular Values: Cumulative Sum')
plt.show()

</t>
<t tx="ekr.20251214045245.106">g.cls()
g.cls()

h = '@jupytext C:/Users/Dev/EKR-Study/python/CODE_PYTHON/CH01/CH01_SEC02.ipynb'
root = g.findNodeAnywhere(c, h)
assert root, h

c.selectPosition(root)
c.executeScript()
</t>
<t tx="ekr.20251214045245.107">&lt;&lt; prefix &gt;&gt;
@others
@language jupytext
@tabwidth -4
</t>
<t tx="ekr.20251214045245.108"># ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

</t>
<t tx="ekr.20251214045245.109"># %%

from matplotlib.image import imread
import matplotlib.pyplot as plt
import numpy as np
import os

# c:/Users/Data is the data folder.</t>
<t tx="ekr.20251214045245.11"></t>
<t tx="ekr.20251214045245.110"># %%

plt.rcParams['figure.figsize'] = [16, 8]
A = imread(os.path.join('..', 'DATA', 'dog.jpg'))
X = np.mean(A, -1); # Convert RGB to grayscale

img = plt.imshow(X)
img.set_cmap('gray')
plt.axis('off')
plt.show()

</t>
<t tx="ekr.20251214045245.111"># %%

U, S, VT = np.linalg.svd(X,full_matrices=False)
S = np.diag(S)

j = 0
for r in (5, 20, 100):
    # Construct approximate image
    Xapprox = U[:,:r] @ S[0:r,:r] @ VT[:r,:]
    plt.figure(j+1)
    j += 1
    img = plt.imshow(Xapprox)
    img.set_cmap('gray')
    plt.axis('off')
    plt.title('r = ' + str(r))
    plt.show()

</t>
<t tx="ekr.20251214045245.112"># %%

plt.figure(1)
plt.semilogy(np.diag(S))
plt.title('Singular Values')
plt.show()

plt.figure(2)
plt.plot(np.cumsum(np.diag(S))/np.sum(np.diag(S)))
plt.title('Singular Values: Cumulative Sum')
plt.show()

</t>
<t tx="ekr.20251214045245.113"></t>
<t tx="ekr.20251214045245.114">@language python

def spam2():
    """A docstring"""
    print('a string')

def spam3():
    pass

class NewClass:
    def f1(self):
        pass
</t>
<t tx="ekr.20251214045245.115">@language rust

fn spam() {
    println!("spam");
}

fn eggs() {
    println!("eggs");
}
</t>
<t tx="ekr.20251214045245.116">@language typescript

public watch(p_resource: vscode.Uri, p_options: { readonly recursive: boolean; readonly excludes: readonly string[] }): vscode.Disposable {
    const w_gnx = utils.leoUriToStr(p_resource);
    if (!this.watchedBodiesGnx.includes(w_gnx)) {
        this.watchedBodiesGnx.push(w_gnx); // add gnx
    }
    // else already in list
    return new vscode.Disposable(() =&gt; {
        const w_position = this.watchedBodiesGnx.indexOf(w_gnx); // find and remove it
        if (w_position &gt; -1) {
            this.watchedBodiesGnx.splice(w_position, 1);
        }
    });
}

public stat(p_uri: vscode.Uri): vscode.FileStat {
    if (this._leoUi.leoStates.fileOpenedReady) {
        const w_gnx = utils.leoUriToStr(p_uri);
        if (p_uri.fsPath.length === 1) {

            return { type: vscode.FileType.Directory, ctime: 0, mtime: 0, size: 0 };
            // SPECIAL CASE -----------------------------------------------
        } else if (w_gnx === this._lastGnx &amp;&amp; this._openedBodiesInfo[this._lastGnx]) {
            return {
                type: vscode.FileType.File,
                ctime: this._openedBodiesInfo[this._lastGnx].ctime,
                mtime: this._openedBodiesInfo[this._lastGnx].mtime,
                size: this._lastBodyLength
            };
            // ------------------------------------------------------------
        } else if (this._openedBodiesInfo[w_gnx]) {
            const c = g.app.windowList[this._leoUi.frameIndex].c;
            const w_v = c.fileCommands.gnxDict[w_gnx];
            return {
                type: vscode.FileType.File,
                ctime: this._openedBodiesInfo[w_gnx].ctime,
                mtime: this._openedBodiesInfo[w_gnx].mtime,
                size: Buffer.byteLength(w_v.b, 'utf8') // w_v.b.length
            };
        }
    }
    // throw vscode.FileSystemError.FileNotFound();
    // (Instead of FileNotFound) should be caught by _onActiveEditorChanged or _changedVisibleTextEditors
    return { type: vscode.FileType.File, ctime: 0, mtime: 0, size: 0 };
}</t>
<t tx="ekr.20251214045245.117"></t>
<t tx="ekr.20251214045245.118">g.cls()
colorer = c.frame.body.colorizer
d = colorer.configDict
keys = [z for z in d if 'literal' in z]
d2 = {key: d[key] for key in keys}
g.printObj(d2)
</t>
<t tx="ekr.20251214045245.119"></t>
<t tx="ekr.20251214045245.12">@language python

def spam():
    pass
    
spam</t>
<t tx="ekr.20251214045245.120">@language html

&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;

&lt;h2&gt;Width and Height Attributes&lt;/h2&gt;

&lt;p&gt;
The width and height attributes of the img tag, defines the width and
height of the image:
&lt;/p&gt;

&lt;img src="img_girl.jpg" width="500" height="600"&gt;

&lt;/body&gt;
&lt;/html&gt;
</t>
<t tx="ekr.20251214045245.121">@language html

&lt;!DOCTYPE html&gt;
&lt;
&lt;body&gt;

&lt;/html&gt;
</t>
<t tx="ekr.20251214045245.122">@language html

&lt;html&gt;

&lt;style&gt;
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
&lt;/style&gt;

&lt;/html&gt;

</t>
<t tx="ekr.20251214045245.123">@language html

&lt;head&gt;

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;

&lt;/head&gt;</t>
<t tx="ekr.20251214045245.124"></t>
<t tx="ekr.20251214045245.125">@language jupytext

@others  # test.


@language python

@others  # test2.</t>
<t tx="ekr.20251214045245.126">@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

A **bold** word.

# %%
import pandas as pd  # A comment

def spam(s: str) -&gt; str:
    print(s)
    
spam('Hi')

    
# %% [markdown]

*Italics* and **bold**.
</t>
<t tx="ekr.20251214045245.127">@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

A **bold** word.
</t>
<t tx="ekr.20251214045245.128">@language jupytext

# %%
def spam(s: str) -&gt; str:
    """A docstring a"""
    return '1234'

</t>
<t tx="ekr.20251214045245.129">@killcolor

@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

A **bold** word.

# %%
import pandas as pd  # A comment

def spam(s: str) -&gt; str:
    return 'hi123'
    
# %% [markdown]

*Italics* and **bold**.
</t>
<t tx="ekr.20251214045245.13">@language rust

fn spam {

}
    
spam</t>
<t tx="ekr.20251214045245.130"># @killcolor

@nocolor
@language python

def spam():
    pass

@color

def spam2():
    pass</t>
<t tx="ekr.20251214045245.131">@language md
@wrap
@nosearch

https://github.com/leo-editor/leo-editor/issues/4146
PR: https://github.com/leo-editor/leo-editor/pull/4147

# Section
## Section

@language python

def spam():
    if </t>
<t tx="ekr.20251214045245.132">@language c

# include abc
#ifdef
#else
    whatever;
#endif

int foo ();

while 1 {
    a = @b;
    goto done;
done:
}

/* Comment */

/** Doxygen comment
*/

/*! Doxygen comment */
</t>
<t tx="ekr.20251214045245.133">@language csharp

// Test

if (abc):
    a = b;</t>
<t tx="ekr.20251214045245.134">@language css

&lt;style&gt;
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
&lt;/style&gt;

</t>
<t tx="ekr.20251214045245.135">@language cweb

% This is limbo in cweb mode... It should be in \LaTeX mode, not \c mode.
% The following should not be colorized: class,if,else.

@* this is a _cweb_ comment.  Code is written in \c.
"strings" should not be colorized.
It should be colored in \LaTeX mode.
The following are not keywords in latex mode: if, else, etc.
Noweb section references are _valid_ in cweb comments!
&lt; &lt; section ref &gt; &gt;
&lt; &lt; missing ref &gt; &gt;
@c

and this is C code. // It is colored in \LaTeX mode by default.
/* This is a C block comment.  It may also be colored in restricted \LaTeX mode. */

// Section refs are valid in code too, of course.
&lt; &lt; section ref &gt; &gt;
&lt; &lt; missing ref &gt; &gt;

\LaTeX and \c should not be colored.
if else, while, do // C keywords.
</t>
<t tx="ekr.20251214045245.136">@language python

# Depends on @bool color-docstrings-as-rest.

# Depends on @bool color-docparts-as-rest.

@
This is *italic*
@c 

"""
Should be *italic*.
"""

'''
Should be **bold**.
'''

# Should be a regular comment.

# Depends on @bool color-docparts-as-rest.

@
This is *bold*
@c 

# Should be a regular comment.</t>
<t tx="ekr.20251214045245.137">@language elisp

; Maybe...
error princ

; More typical of other lisps...
and apply
car cdr cons cond
defconst defun defvar
eq equal eval
gt ge
if
let le lt
mapcar
ne nil
or not
prog progn
set setq
t type-of
unless
when while</t>
<t tx="ekr.20251214045245.138">@language glsl

if 1 {

}</t>
<t tx="ekr.20251214045245.139">@language javascript

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;
</t>
<t tx="ekr.20251214045245.14"></t>
<t tx="ekr.20251214045245.140">@language md

# Loading and Analyzing Data
# ==========================

*Italics* and **bold**.
More markdown. nxy

Here is come python code::

    print('Hi')
</t>
<t tx="ekr.20251214045245.141">@language nim

sub: RootObj = huh #
n: Natural = range(0..9)
r: Rune = 'é'
a: any = none
b: bool = false
s: string = "Single string"
f1: float64 = 0.0d
f2: float32 = -.0f32
f3: float = 1f+1
i: int = 1_000_000
u'abd = 2u32
var x = 5'u4
c = 'abc'

#[ A multi-line comment.
    #[
        Blah.
    ]#
]#

# GitHub colors all other constants blue, including system constants:
42
3.5
AllocStats
DivByZeroError
BiggestUInt
Slice
SomeFloat

# GitHub colors system functions purple:
echo "Hi"
alloc()

# GitHub does not color modules that "system" imports.
import exceptions  # Imported by system module.
import Exception  # Defined in system module.
</t>
<t tx="ekr.20251214045245.142">@language python
def spam(s: str) -&gt; str:
    """A docstring a"""
    g.es_print(s)

spam('1234')</t>
<t tx="ekr.20251214045245.143">@language python

import pandas as pd  # A comment

def spam(s: str) -&gt; str:
    return 'hi'
    
@language md

# Loading and Analyzing Data
# ==========================

*Italics* and **bold**

More markdown. 
</t>
<t tx="ekr.20251214045245.144">@language rust

/// Test `a` *itilic* **bold**</t>
<t tx="ekr.20251214045245.145">@language rest
@wrap
@nosearch

https://github.com/leo-editor/leo-editor/issues/4146
PR: https://github.com/leo-editor/leo-editor/pull/4147

- Item 1
- Item 2.

***A label.

.. comment.

@language python

def spam(s: str) -&gt; str:
    g.es_print(s)
    
spam('Hi')</t>
<t tx="ekr.20251214045245.146">@language rust

/// Triple comment.

// Characters, length 10 and 6.
char10 = '\u{7fff}'
char6 = '\x7f'

// Characters, length 4.
nl = '\n'
null = '\0'
tab = '\t'
ret = '\r'
bs = '\\'

// Characters, length 3.
a = 'x' 
b = 'é'

// Lifetimes.
pub fn &lt;'a&gt;:
'a
'static
'static&gt;

// Errors.
other 'xx
bad_escaple '\y'
</t>
<t tx="ekr.20251214045245.147">@language vue

&lt;template&gt;
  &lt;body&gt;
    &lt;h2&gt;Width and Height Attributes&lt;/h2&gt;
    &lt;p class="greeting"&gt;{{ greeting }}&lt;/p&gt;
    &lt;p&gt;
    The width and height attributes of the img tag, defines the width and
    height of the image:
    &lt;/p&gt;
    &lt;img src="img_girl.jpg" width="500" height="600"&gt;
  &lt;/body&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;

&lt;style&gt;
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
.greeting {
  color: red;
  font-weight: bold;
}
&lt;/style&gt;

&lt;template&gt;
  &lt;p class="greeting"&gt;{{ greeting 3 }}&lt;/p&gt;
&lt;/template&gt;</t>
<t tx="ekr.20251214045245.148">@language javascript

&lt;script setup&gt;
import { ref } from 'vue'
const greeting = ref('Hello World!')
&lt;/script&gt;

@language vue

&lt;template&gt;
  &lt;p class="greeting"&gt;{{ greeting }}&lt;/p&gt;
&lt;/template&gt;

@language css

&lt;style&gt;
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
.greeting {
  color: red;
  font-weight: bold;
}
&lt;/style&gt;</t>
<t tx="ekr.20251214045245.149">http://www.google.com/search</t>
<t tx="ekr.20251214045245.15">command = r'python c:\Python\Python3.13\Lib\turtledemo\penrose.py'
g.execute_shell_commands(command)

</t>
<t tx="ekr.20251214045245.150">@
line 1
@
another line
line 2
@c
@c
</t>
<t tx="ekr.20251214045245.16">@language html
@wrap
@tabwidth -2</t>
<t tx="ekr.20251214045245.17">@language md

&lt;h2&gt;Feed-forward Network Functions&lt;/h2&gt;
We will consider a basic two layer neural network model, i.e a model that maps inputs to a hidden layer and then to an output layer. We will make **the following assumptions**

1. The final output will be a vector $Y$ with $K$ elements, $y_k$, where $y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})$ is the probability that node $k$ is in class $C_1$ and $p(C_2|\mathbf{x}) = 1-p(C_1|\mathbf{x})$
2. The activation function at a given layer is an arbitrary nonlinear function of a linear combination of the inputs and parameters for that layer
3. The network is fully connected, i.e. every node at the input layer is connected to every node in the hidden layer and every node in the hidden layer is connected to every node in the output layer
4. A bias parameter is included at the hidden and output layers

Working from the input layer toward the output layer, we can build this model as follows:

&lt;h3&gt;Input Layer&lt;/h3&gt;
Assume we have an input vector $\mathbf{x} \in \Re^D$. Then the input layer consists of $D+1$ nodes where the value of the $i^{th}$ node for $i=0\ldots D$, is 0 if $i=0$ and $x_i$, i.e. the $i^{th}$ value of $\mathbf{x}$, otherwise.

&lt;h3&gt;Hidden Layer&lt;/h3&gt;
At the hidden layer we construct $M$ nodes where the value of $M$ depends on the specifics of the particular modeling problem. For each node, we define a *unit activation*, $a_m$, for $m=1\ldots M$ as &lt;br/&gt;
$a_m = \sum_{i=0}^D w_{ji}^{(1)}x_i$ &lt;br/&gt;
where the $(1)$ superscript indicates this weight is for the hidden layer. The output from each node, $z_m$, is then given by the value of a *fixed nonlinear function*, $h$, known as the *activation function*, acting on the unit activation&lt;br/&gt;
$z_m = h(a_m) = h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right)$&lt;br/&gt;
Notice that $h$ is the same function for all nodes.
&lt;h3&gt;Output Layer&lt;/h3&gt;
The process at the output layer is essentially the same as at the hidden layer. We construct $K$ nodes, where again the value of $K$ depends on the specific modeling problem. For each node, we again define a *unit activation*, $a_k$, for $k=1 \ldots K$ by&lt;br/&gt;
$a_k = \sum_{m=0}^M w_{km}^{(2)} z_m$ &lt;br/&gt;
We again apply a nonlinear activation function, say $y$, to produce the output&lt;br/&gt;
$y_k = y(a_k)$

Thus, the entire model can be summarized as a $K$ dimensional output vector $Y \in \Re^K$ where each element $y_k$ by&lt;br/&gt;
$y_k(\mathbf{x},\mathbf{w}) = y \left( \sum_{m=0}^M w_{km}^{(2)} h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right) \right)$

&lt;h3&gt;Generalizations&lt;/h3&gt;
There are a wide variety of generalizations possible for this model. Some of the more important ones for practical applications include

* Addition of hidden layers
* Inclusion of *skip-layer* connections, e.g. a connection from an input node directly to an output node
* Sparse network, i.e. not a fully connected network</t>
<t tx="ekr.20251214045245.18">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;

&lt;script type="text/x-mathjax-config"&gt;
  MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});
&lt;/script&gt;

&lt;script type="text/javascript" async
  src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_CHTML"&gt;
&lt;/script&gt;


&lt;/head&gt;

&lt;body&gt;
&lt;p&gt;&lt;mathjax&gt;

&lt;h2&gt;Feed-forward Network Functions&lt;/h2&gt;
We will consider a basic two layer neural network model, i.e a model that maps inputs to a hidden layer and then to an output layer. We will make &lt;b&gt;the following 
assumptions&lt;/b&gt;

1. The final output will be a vector $Y$ with $K$ elements, $y_k$, where $y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})$ is the probability that node $k$ is in class $C_1$ and $p(C_2|\mathbf{x}) = 1-p(C_1|\mathbf{x})$
2. The activation function at a given layer is an arbitrary nonlinear function of a linear combination of the inputs and parameters for that layer
3. The network is fully connected, i.e. every node at the input layer is connected to every node in the hidden layer and every node in the hidden layer is connected to every node in the output layer
4. A bias parameter is included at the hidden and output layers

Working from the input layer toward the output layer, we can build this model as follows:

&lt;h3&gt;Input Layer&lt;/h3&gt;
Assume we have an input vector $\mathbf{x} \in \Re^D$. Then the input layer consists of $D+1$ nodes where the value of the $i^{th}$ node for $i=0\ldots D$, is 0 if $i=0$ and $x_i$, i.e. the $i^{th}$ value of $\mathbf{x}$, otherwise.

&lt;h3&gt;Hidden Layer&lt;/h3&gt;
At the hidden layer we construct $M$ nodes where the value of $M$ depends on the specifics of the particular modeling problem. For each node, we define a *unit activation*, $a_m$, for $m=1\ldots M$ as &lt;br/&gt;
$a_m = \sum_{i=0}^D w_{ji}^{(1)}x_i$ &lt;br/&gt;
where the $(1)$ superscript indicates this weight is for the hidden layer. The output from each node, $z_m$, is then given by the value of a *fixed nonlinear function*, $h$, known as the *activation function*, acting on the unit activation&lt;br/&gt;
$z_m = h(a_m) = h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right)$&lt;br/&gt;
Notice that $h$ is the same function for all nodes.
&lt;h3&gt;Output Layer&lt;/h3&gt;
The process at the output layer is essentially the same as at the hidden layer. We construct $K$ nodes, where again the value of $K$ depends on the specific modeling problem. For each node, we again define a *unit activation*, $a_k$, for $k=1 \ldots K$ by&lt;br/&gt;
$a_k = \\sum_{m=0}^M w_{km}^{(2)} z_m$ &lt;br/&gt;
We again apply a nonlinear activation function, say $y$, to produce the output&lt;br/&gt;
$y_k = y(a_k)$

Thus, the entire model can be summarized as a $K$ dimensional output vector $Y \in \Re^K$ where each element $y_k$ by&lt;br/&gt;
$y_k(\mathbf{x},\mathbf{w}) = y \left( \sum_{m=0}^M w_{km}^{(2)} h \left( \sum_{i=0}^D w_{mi}^{(1)}x_i \right) \right)$

&lt;h3&gt;Generalizations&lt;/h3&gt;
There are a wide variety of generalizations possible for this model. Some of the more important ones for practical applications include

* Addition of hidden layers
* Inclusion of *skip-layer* connections, e.g. a connection from an input node directly to an output node
* Sparse network, i.e. not a fully connected network

&lt;!-- $$ --&gt;
&lt;/mathjax&gt;&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;

</t>
<t tx="ekr.20251214045245.19">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;!-- saved from url=(0043)file:///C:/Users/tom/.leo/leo_rst_html.html --&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;&lt;head&gt;&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;

&lt;meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/"&gt;
&lt;title&gt;Formula&lt;/title&gt;
&lt;script type="text/javascript" src="./vr3-mathjax-sample_files/tex-mml-chtml.js.download"&gt;&lt;/script&gt;&lt;style type="text/css"&gt;.CtxtMenu_InfoClose {  top:.2em; right:.2em;}
.CtxtMenu_InfoContent {  overflow:auto; text-align:left; font-size:80%;  padding:.4em .6em; border:1px inset; margin:1em 0px;  max-height:20em; max-width:30em; background-color:#EEEEEE;  white-space:normal;}
.CtxtMenu_Info.CtxtMenu_MousePost {outline:none;}
.CtxtMenu_Info {  position:fixed; left:50%; width:auto; text-align:center;  border:3px outset; padding:1em 2em; background-color:#DDDDDD;  color:black;  cursor:default; font-family:message-box; font-size:120%;  font-style:normal; text-indent:0; text-transform:none;  line-height:normal; letter-spacing:normal; word-spacing:normal;  word-wrap:normal; white-space:nowrap; float:none; z-index:201;  border-radius: 15px;                     /* Opera 10.5 and IE9 */  -webkit-border-radius:15px;               /* Safari and Chrome */  -moz-border-radius:15px;                  /* Firefox */  -khtml-border-radius:15px;                /* Konqueror */  box-shadow:0px 10px 20px #808080;         /* Opera 10.5 and IE9 */  -webkit-box-shadow:0px 10px 20px #808080; /* Safari 3 &amp; Chrome */  -moz-box-shadow:0px 10px 20px #808080;    /* Forefox 3.5 */  -khtml-box-shadow:0px 10px 20px #808080;  /* Konqueror */  filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color="gray", Positive="true"); /* IE */}
&lt;/style&gt;&lt;style type="text/css"&gt;.CtxtMenu_MenuClose {  position:absolute;  cursor:pointer;  display:inline-block;  border:2px solid #AAA;  border-radius:18px;  -webkit-border-radius: 18px;             /* Safari and Chrome */  -moz-border-radius: 18px;                /* Firefox */  -khtml-border-radius: 18px;              /* Konqueror */  font-family: "Courier New", Courier;  font-size:24px;  color:#F0F0F0}
.CtxtMenu_MenuClose span {  display:block; background-color:#AAA; border:1.5px solid;  border-radius:18px;  -webkit-border-radius: 18px;             /* Safari and Chrome */  -moz-border-radius: 18px;                /* Firefox */  -khtml-border-radius: 18px;              /* Konqueror */  line-height:0;  padding:8px 0 6px     /* may need to be browser-specific */}
.CtxtMenu_MenuClose:hover {  color:white!important;  border:2px solid #CCC!important}
.CtxtMenu_MenuClose:hover span {  background-color:#CCC!important}
.CtxtMenu_MenuClose:hover:focus {  outline:none}
&lt;/style&gt;&lt;style type="text/css"&gt;.CtxtMenu_Menu {  position:absolute;  background-color:white;  color:black;  width:auto; padding:5px 0px;  border:1px solid #CCCCCC; margin:0; cursor:default;  font: menu; text-align:left; text-indent:0; text-transform:none;  line-height:normal; letter-spacing:normal; word-spacing:normal;  word-wrap:normal; white-space:nowrap; float:none; z-index:201;  border-radius: 5px;                     /* Opera 10.5 and IE9 */  -webkit-border-radius: 5px;             /* Safari and Chrome */  -moz-border-radius: 5px;                /* Firefox */  -khtml-border-radius: 5px;              /* Konqueror */  box-shadow:0px 10px 20px #808080;         /* Opera 10.5 and IE9 */  -webkit-box-shadow:0px 10px 20px #808080; /* Safari 3 &amp; Chrome */  -moz-box-shadow:0px 10px 20px #808080;    /* Forefox 3.5 */  -khtml-box-shadow:0px 10px 20px #808080;  /* Konqueror */}
.CtxtMenu_MenuItem {  padding: 1px 2em;  background:transparent;}
.CtxtMenu_MenuArrow {  position:absolute; right:.5em; padding-top:.25em; color:#666666;  font-family: null; font-size: .75em}
.CtxtMenu_MenuActive .CtxtMenu_MenuArrow {color:white}
.CtxtMenu_MenuArrow.CtxtMenu_RTL {left:.5em; right:auto}
.CtxtMenu_MenuCheck {  position:absolute; left:.7em;  font-family: null}
.CtxtMenu_MenuCheck.CtxtMenu_RTL { right:.7em; left:auto }
.CtxtMenu_MenuRadioCheck {  position:absolute; left: .7em;}
.CtxtMenu_MenuRadioCheck.CtxtMenu_RTL {  right: .7em; left:auto}
.CtxtMenu_MenuInputBox {  padding-left: 1em; right:.5em; color:#666666;  font-family: null;}
.CtxtMenu_MenuInputBox.CtxtMenu_RTL {  left: .1em;}
.CtxtMenu_MenuComboBox {  left:.1em; padding-bottom:.5em;}
.CtxtMenu_MenuSlider {  left: .1em;}
.CtxtMenu_SliderValue {  position:absolute; right:.1em; padding-top:.25em; color:#333333;  font-size: .75em}
.CtxtMenu_SliderBar {  outline: none; background: #d3d3d3}
.CtxtMenu_MenuLabel {  padding: 1px 2em 3px 1.33em;  font-style:italic}
.CtxtMenu_MenuRule {  border-top: 1px solid #DDDDDD;  margin: 4px 3px;}
.CtxtMenu_MenuDisabled {  color:GrayText}
.CtxtMenu_MenuActive {  background-color: #606872;  color: white;}
.CtxtMenu_MenuDisabled:focus {  background-color: #E8E8E8}
.CtxtMenu_MenuLabel:focus {  background-color: #E8E8E8}
.CtxtMenu_ContextMenu:focus {  outline:none}
.CtxtMenu_ContextMenu .CtxtMenu_MenuItem:focus {  outline:none}
.CtxtMenu_SelectionMenu {  position:relative; float:left;  border-bottom: none; -webkit-box-shadow:none; -webkit-border-radius:0px; }
.CtxtMenu_SelectionItem {  padding-right: 1em;}
.CtxtMenu_Selection {  right: 40%; width:50%; }
.CtxtMenu_SelectionBox {  padding: 0em; max-height:20em; max-width: none;  background-color:#FFFFFF;}
.CtxtMenu_SelectionDivider {  clear: both; border-top: 2px solid #000000;}
.CtxtMenu_Menu .CtxtMenu_MenuClose {  top:-10px; left:-10px}
&lt;/style&gt;
&lt;style type="text/css"&gt;

/* +---------------+
/* | normalize.css |
 * +---------------+
 */

/**
 * Sources
 *1. Docutils solarized-dark theme adapted from
 *    https://github.com/fladd/docutils-solarized
 *
 *2. Pygments additions by T. B. Passin.
 */

/*! normalize.css v3.0.2 | MIT License | git.io/normalize */

/**
 * 1. Set default font family to sans-serif.
 * 2. Prevent iOS text size adjust after orientation change, without disabling
 *    user zoom.
 */

html {
  font-family: sans-serif; /* 1 */
  -ms-text-size-adjust: 100%; /* 2 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/**
 * Remove default margin.
 */

body {
  margin: 0;
}

/* HTML5 display definitions
   ========================================================================== */

/**
 * Correct `block` display not defined for any HTML5 element in IE 8/9.
 * Correct `block` display not defined for `details` or `summary` in IE 10/11
 * and Firefox.
 * Correct `block` display not defined for `main` in IE 11.
 */

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
  display: block;
}

/**
 * 1. Correct `inline-block` display not defined in IE 8/9.
 * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
 */

audio,
canvas,
progress,
video {
  display: inline-block; /* 1 */
  vertical-align: baseline; /* 2 */
}

/**
 * Prevent modern browsers from displaying `audio` without controls.
 * Remove excess height in iOS 5 devices.
 */

audio:not([controls]) {
  display: none;
  height: 0;
}

/**
 * Address `[hidden]` styling not present in IE 8/9/10.
 * Hide the `template` element in IE 8/9/11, Safari, and Firefox &lt; 22.
 */

[hidden],
template {
  display: none;
}

/* Links
   ========================================================================== */

/**
 * Remove the gray background color from active links in IE 10.
 */

a {
  background-color: transparent;
}

/**
 * Improve readability when focused and also mouse hovered in all browsers.
 */

a:active,
a:hover {
  outline: 0;
}

/* Text-level semantics
   ========================================================================== */

/**
 * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
 */

abbr[title] {
  border-bottom: 1px dotted;
}

/**
 * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
 */

b,
strong {
  font-weight: bold;
}

/**
 * Address styling not present in Safari and Chrome.
 */

dfn {
  font-style: italic;
}

/**
 * Address variable `h1` font-size and margin within `section` and `article`
 * contexts in Firefox 4+, Safari, and Chrome.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/**
 * Address styling not present in IE 8/9.
 */

mark {
  background: #ff0;
  color: #000;
}

/**
 * Address inconsistent and variable font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` affecting `line-height` in all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sup {
  top: -0.5em;
}

sub {
  bottom: -0.25em;
}

/* Embedded content
   ========================================================================== */

/**
 * Remove border when inside `a` element in IE 8/9/10.
 */

img {
  border: 0;
}

/**
 * Correct overflow not hidden in IE 9/10/11.
 */

svg:not(:root) {
  overflow: hidden;
}

/* Grouping content
   ========================================================================== */

/**
 * Address margin not present in IE 8/9 and Safari.
 */

figure {
  margin: 1em 40px;
}

/**
 * Address differences between Firefox and other browsers.
 */

hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  height: 0;
}

/**
 * Contain overflow in all browsers.
 */

pre {
  overflow: auto;
}

/**
 * Address odd `em`-unit font size rendering in all browsers.
 */

code,
kbd,
pre,
samp {
  font-family: monospace, monospace;
  font-size: 1em;
}

/* Forms
   ========================================================================== */

/**
 * Known limitation: by default, Chrome and Safari on OS X allow very limited
 * styling of `select`, unless a `border` property is set.
 */

/**
 * 1. Correct color not being inherited.
 *    Known issue: affects color of disabled elements.
 * 2. Correct font properties not being inherited.
 * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
 */

button,
input,
optgroup,
select,
textarea {
  color: inherit; /* 1 */
  font: inherit; /* 2 */
  margin: 0; /* 3 */
}

/**
 * Address `overflow` set to `hidden` in IE 8/9/10/11.
 */

button {
  overflow: visible;
}

/**
 * Address inconsistent `text-transform` inheritance for `button` and `select`.
 * All other form control elements do not inherit `text-transform` values.
 * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
 * Correct `select` style inheritance in Firefox.
 */

button,
select {
  text-transform: none;
}

/**
 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
 *    and `video` controls.
 * 2. Correct inability to style clickable `input` types in iOS.
 * 3. Improve usability and consistency of cursor style between image-type
 *    `input` and others.
 */

button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
  -webkit-appearance: button; /* 2 */
  cursor: pointer; /* 3 */
}

/**
 * Re-set default cursor for disabled elements.
 */

button[disabled],
html input[disabled] {
  cursor: default;
}

/**
 * Remove inner padding and border in Firefox 4+.
 */

button::-moz-focus-inner,
input::-moz-focus-inner {
  border: 0;
  padding: 0;
}

/**
 * Address Firefox 4+ setting `line-height` on `input` using `!important` in
 * the UA stylesheet.
 */

input {
  line-height: normal;
}

/**
 * It's recommended that you don't attempt to style these elements.
 * Firefox's implementation doesn't respect box-sizing, padding, or width.
 *
 * 1. Address box sizing set to `content-box` in IE 8/9/10.
 * 2. Remove excess padding in IE 8/9/10.
 */

input[type="checkbox"],
input[type="radio"] {
  box-sizing: border-box; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Fix the cursor style for Chrome's increment/decrement buttons. For certain
 * `font-size` values of the `input`, it causes the cursor style of the
 * decrement button to change from `default` to `text`.
 */

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
 * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
 *    (include `-moz` to future-proof).
 */

input[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  -moz-box-sizing: content-box;
  -webkit-box-sizing: content-box; /* 2 */
  box-sizing: content-box;
}

/**
 * Remove inner padding and search cancel button in Safari and Chrome on OS X.
 * Safari (but not Chrome) clips the cancel button when the search input has
 * padding (and `textfield` appearance).
 */

input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * Define consistent border, margin, and padding.
 */

fieldset {
  border: 1px solid #c0c0c0;
  margin: 0 2px;
  padding: 0.35em 0.625em 0.75em;
}

/**
 * 1. Correct `color` not being inherited in IE 8/9/10/11.
 * 2. Remove padding so people aren't caught out if they zero out fieldsets.
 */

legend {
  border: 0; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Remove default vertical scrollbar in IE 8/9/10/11.
 */

textarea {
  overflow: auto;
}

/**
 * Don't inherit the `font-weight` (applied by a rule above).
 * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
 */

optgroup {
  font-weight: bold;
}

/* Tables
   ========================================================================== */

/**
 * Remove most spacing between table cells.
 */

table {
  border-collapse: collapse;
  border-spacing: 0;
}

td,
th {
  padding: 0;
}



/* +--------------------+
 * | docutils-solarized |
 * +--------------------+
 */

html {
  transform: scale(.85);
  transform-origin: 0 0;

}

/* Document Structure
 * ==================
 */

/* Document */

body {
  color: #839496;
  background: #002b36;
  /*font-family: Palatino, "Palatino LT STD", "Palatino Linotype", "Book Antiqua", Georgia, serif;*/
  font-family: Verdana, Arial, "Bitstream Vera Sans", sans-serif;
  padding: 0 5%;
  margin: 8px 0;
}

div.document {
  font-size: 1em;
  line-height:1.5;
  counter-reset: table;
  max-width: 50em;
  margin: auto;
}

.align-left  { text-align: left; }
.align-right { text-align: right; }
.align-center {
  clear: both;
  text-align: center;
}

/* Sections */

h1.title, p.subtitle {
  text-align: center;
}

h1, h2, h3, h4, h5, h6 {
  margin-top: 1em;
  margin-bottom: 0em;
}

h1.title {
  font-size: 2em;
  margin-top: 4rem;  
}
h1 + p.subtitle {
  font-size: 1.5em;
  margin-top: 1em;
  margin-bottom: 4rem;
}
h1 + p.section-subtitle { font-size: 1.6em; }
h2 + p.section-subtitle { font-size: 1.28em; }

a.toc-backref {
  color: #839496;
  text-decoration: none;
}

a.toc-backref:visited {
  color: #839496;
  text-decoration: none;
}

/* Stop floating sidebars, images and figures at section level 1,2,3 */
h1, h2, h3 { clear: both; }

/* Transitions */

hr.docutils {
  width: 100%;
  margin-top: 2em;
  margin-bottom: 2em;
  clear: both;
  border: 1px solid;
}

/* Paragraphs
 * ==========
 */

/* vertical space (parskip) */
p, ol, ul, dl,
div.line-block,
table, pre, .figure {
  margin-top: 1em;
  margin-bottom: 1em;
}

dl &gt; dd {
  margin-bottom: 1em;
}

/* titles */
p.admonition-title,
p.topic-title,
p.sidebar-title,
p.system-message-title {
  color: #ededed;
  font-weight: bold;
}

p.subtitle,
p.section-subtitle,
p.sidebar-subtitle {
  font-weight: bold;
  margin-top: -0.5em;
}

p.sidebar-subtitle {
  color: #ededed;
}

/* Warnings, Errors */
div.admonition {
  background-color: #073642;
  /*color: #ededed;*/
}

div.danger p.admonition-title,
div.error p.admonition-title,
div.system-messages h1,
div.error,
span.problematic,
p.system-message-title {
  color: #dc322f;
}

div.caution p.admonition-title,
div.attention p.admonition-title,
div.warning p.admonition-title {
  color: #cb4b16;
}

div.hint p.admonition-title,
div.tip p.admonition-title {
  color: #859900;
}

div.important p.admonition-title {
  color: #d33682;
}

div.note p.admonition-title {
  color: #2aa198;
}

/* Lists
/* =====
 */

/* compact and simple lists: no margin between items */
dl.simple &gt; dd, dl.compact &gt; dd,
.compact li, .compact ul, .compact ol
.simple  li, .simple  ul, .simple  ol,
.simple &gt; li p, .compact &gt; li p {
  margin-top: 0;
  margin-bottom: 0;
}

/* Enumerated Lists */

ol.arabic     { list-style: decimal }
ol.loweralpha { list-style: lower-alpha }
ol.upperalpha { list-style: upper-alpha }
ol.lowerroman { list-style: lower-roman }
ol.upperroman { list-style: upper-roman }

/* Definition Lists */

dl &gt; dd p:first-child { margin-top: 0; }

/* lists nested in definition lists */
dd &gt; ul, dd &gt; ol { padding-left: 0pt; }

dt span.classifier { font-style: italic }
dt span.classifier:before {
  font-style: normal;
  margin: 0.5em;
  content: ":";
}

/* Field Lists */

/* bold field name, content starts on the same line */
dl.field-list &gt; dt,
dl.option-list &gt; dt,
dl.docinfo &gt; dt,
dl.footnote &gt; dt,
dl.citation &gt; dt,
dl.description &gt; dt {
  font-weight: bold;
  clear: left;
  float: left;
  margin: 0;
  padding: 0;
  padding-right: 0.5em;
}

/* Offset for field content (corresponds to the --field-name-limit option) */
dl.field-list &gt; dd,
dl.option-list &gt; dd,
dl.docinfo &gt; dd {
  margin-left:  9em; /* ca. 14 chars in the test examples */
}

/* start field-body on a new line after long field names */
dl.field-list &gt; dd &gt; *:first-child,
dl.option-list &gt; dd &gt; *:first-child,
dl.docinfo &gt; dd  &gt; *:first-child {
  display: inline-block;
  width: 100%;
  margin: 0;
}

/* field names followed by a colon */
dl.field-list &gt; dt:after,
dl.docinfo    &gt; dt:after {
  content: ":";
}

/* example for custom field-name width */
dl.field-list.narrow &gt; dd {
  margin-left: 5em;
}

/* run-in: start field-body on same line after long field names */
dl.field-list.run-in &gt; dd p {
  display: block;
}

/* Bibliographic Fields */

/* use special field-list dl.docinfo */

pre.address {
  margin-bottom: 0;
  margin-top: 0;
  font: inherit;
}

dd.authors &gt; p { margin: 0; }

div.abstract {
  margin-top: 2em;
  margin-bottom: 2em;
  text-align: center;
}

div.dedication {
  margin-top: 2em;
  margin-bottom: 2em;
  text-align: center;
  font-style: italic;
}

div.dedication div.abstract  p.topic-title {
  font-style: normal;
}

/* Option Lists */

dl.option-list {
  margin-left: 1em;
}

dl.option-list &gt; dt {
  font-weight: normal;
}

span.option {
  color: #ededed;
  background-color: #073642;
  border: 1px solid #586e75;
  white-space: nowrap;
}

/* Text Blocks
 * ===========
 */

/* Line Blocks */

div.line-block {
  display: block;
}

div.line-block div.line-block {
  margin-top: 0;
  margin-bottom: 0;
  margin-left: 1.5em;
}

/* Literal Blocks */

pre.doctest-block,
pre.math, pre.code {
  /*color: #93a1a1;*/
  color: #ededed;
  background: #073642;
  font-family: Cousine, Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  border: 1px solid #586e75 !important;
  padding: 1em;
  border-radius: .7em;

}

pre.literal-block {
  color: #eee8d5;
  background: #586e75;
  font-family: Cousine, Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  border: 1px solid #b0b0b0;
  padding: 1em;
  border-radius: .7em;
}

/* Block Quotes */

blockquote,
div.topic {
  margin-left: 2em;
  margin-right: 2em
}

blockquote &gt; table,
div.topic &gt; table {
  margin-top: 0;
  margin-bottom: 0;
}

blockquote p.attribution,
div.topic p.attribution {
  text-align: right;
  margin-left: 20%;
}

/* Tables
 * ======
 */

/* margins and borders for "normal" tables */
table {
  border-collapse: collapse;
  border-top: 2px solid;
  border-bottom: 2px solid;
}

td, th {
  padding: 0.5ex 1ex;
  text-align: left;
/* some borders missing at some magnifications
/* in Firefox 31.5.0 and opera 10.63 */
  border-width: 1px;
}

td &gt; p:first-child, th &gt; p:first-child {
  margin-top: 0;
}

td &gt; p, th &gt; p {
  margin-bottom: 0;
}

th {
  background: #586e75;
  color: #eee8d5;
  vertical-align: top;
  border-bottom: thin solid #839496;
  text-align: center;
}

th.docinfo-name {
  text-align: right;
}


table &gt; caption {
  text-align: left;
}

table.borderless td, table.borderless th {
  border: 0;
  padding: 0;
  padding-right: 0.5em /* separate table cells */
}

/* "booktabs" style (no vertical lines) */
table.booktabs {
  border: 0;
  border-top: 2px solid;
  border-bottom: 2px solid;
  border-collapse: collapse;
}

table.booktabs * {
  border: 0;
}

table.booktabs th {
  border-bottom: thin solid;
  text-align: left;
}

/* numbered tables (counter defined in div.document) */
table.numbered &gt; caption:before {
  counter-increment: table;
  content: "Table " counter(table) ": ";
  font-weight: bold;
}

/* Explicit Markup Blocks
 * ======================
 */

/* Footnotes and Citations
 * -----------------------
 */

/* line on the left */
dl.footnote {
  padding-left: 1ex;
  border-left: solid;
  border-left-width: 2px;
  border-color: #839496;
}

dl &gt; dt.label {
  font-weight: normal;
}

dt.label &gt; span.fn-backref {
  margin: 0.2em;
}

dt.label &gt; span.fn-backref &gt; a {
  font-style: italic;
}

/* Directives
*  ----------
*/

/* Admonitions */
/* System Messages */

div.admonition,
div.system-message {
  margin: 2em;
  border: 2px solid;
  padding-right: 1em;
  padding-left: 1em;
  background-color: #073642;
  /*color: #ededed;*/
  border-color: #839496;
}

/* Body Elements
*  -------------
*/

/* Image and Figure */

img {
  vertical-align: bottom;
}

div.figure {
  margin-left: 2em;
  margin-right: 2em;
}

img.align-left,
.figure.align-left,
object.align-left {
  clear: left;
  float: left;
  margin-right: 1em
}

img.align-right,
.figure.align-right,
object.align-right {
  clear: right;
  float: right;
  margin-left: 1em
}

img.align-center,
.figure.align-center,
object.align-center {
  display: block;
  margin-left: auto;
  margin-right: auto;
}

/* reset inner alignment in figures */
div.align-right {
  text-align: inherit }

p.caption {
  font-style: italic;
}

object[type="image/svg+xml"], 
object[type="application/x-shockwave-flash"] {
  overflow: hidden;
}

/* Topic */

#table-of-contents {
  margin-top: 2em;
  margin-bottom: 2em;
}

/* Sidebar */

/* in a layout with fixed margins,		       */
/* the sidebar can be moved into the margin completely */
div.sidebar {
  border: 2px solid;
  border-color: #839496;
  padding-right: 1em;
  padding-left: 1em;
  width: 36%;
  max-width: 26em;
  float: right;
  clear: right;
  margin-left: 1em;
  margin-top: 2em;
  margin-bottom: 2em;
  background-color: #073642;
  color: #ededed;
}

p.sidebar-title { font-size: larger; }

/* Code */

pre.code, code {
  color: #ededed;
  background-color: #073642
}

pre.code .ln {color: #586e75}
pre.code .err { color: #dc322f }                    /* Error */

pre.code .keyword { color: #859900 }                      /* Keyword */
pre.code .keyword.constant { color: #859900 }                     /* Keyword.Constant */
pre.code .keyword.declaration { color: #859900 }                     /* Keyword.Declaration */
pre.code .keyword.namespace { color: #cb4b16 }                     /* Keyword.Namespace */
pre.code .keyword.pseudo { color: #cb4b16 }                     /* Keyword.Pseudo */
pre.code .keyword.reversed { color: #859900 }                     /* Keyword.Reserved */
pre.code .keyword.type { color: #859900 }                     /* Keyword.Type */

pre.code .name { color: #ededed }                      /* Name */
pre.code .name.attribue { }                                    /* Name.Attribute */
pre.code .name.bultin { color: #268bd2 }                     /* Name.Builtin */
pre.code .builtin.class { color: #268bd2 }                     /* Name.Builtin.Pseudo */
pre.code .name.class { color: #268bd2 }                     /* Name.Class */
pre.code .name.constant { color: #b58900 }                     /* Name.Constant */
pre.code .name.decorator { color: #cb4b16 }                     /* Name.Decorator */
pre.code .name.entity { color: #cb4b16 }                     /* Name.Entity */
pre.code .name.exception { color: #b58900 }                     /* Name.Exception */
pre.code .name.function { color: #268bd2 }                     /* Name.Function */
pre.code .name.label { }                                    /* Name.Label */
pre.code .name.namespace { }                                    /* Name.Namespace */
pre.code .name.other { }                                    /* Name.Other */
pre.code .name.property { color: #268bd2 }                     /* Name.Property */
pre.code .name.tag { color: #859900 }                     /* Name.Tag */
pre.code .name.variable { color: #cb4b16 }                     /* Name.Variable */
pre.code .name.variable.class { color: #268bd2 }                     /* Name.Variable.Class */
pre.code .name.variable.global { color: #268bd2 }                     /* Name.Variable.Global */
pre.code .name.variable.instance { color: #268bd2 }                     /* Name.Variable.Instance */

pre.code .literal { }                                     /* Literal */
pre.code .literal.date { }                                    /* Literal.Date */
pre.code .literal.string { color: #2aa198 }                      /* Literal.String */
pre.code .literal.string.backtick { color: #2aa198 }                     /* Literal.String.Backtick */
pre.code .literal.string.char { color: #2aa198 }                     /* Literal.String.Char */
pre.code .literal.string.doc { color: #2aa198 }                     /* Literal.String.Doc */
pre.code .literal.string.double { color: #2aa198 }                     /* Literal.String.Double */
pre.code .literal.string.escape { color: #cb4b16 }                     /* Literal.String.Escape */
pre.code .literal.string.heredoc { color: #2aa198 }                     /* Literal.String.Heredoc */
pre.code .literal.string.interpol { color: #cb4b16 }                     /* Literal.String.Interpol */
pre.code .literal.string.other { color: #2aa198 }                     /* Literal.String.Other */
pre.code .literal.string.regex { color: #2aa198 }                     /* Literal.String.Regex */
pre.code .literal.string.single { color: #2aa198 }                     /* Literal.String.Single */
pre.code .literal.string.symbol { color: #2aa198 }                     /* Literal.String.Symbol */
pre.code .literal.number { color: #2aa198 }                      /* Literal.Number */
pre.code .literal.number.bin { color: #2aa198 }                     /* Literal.Number.Bin */
pre.code .literal.number.float { color: #2aa198 }                     /* Literal.Number.Float */
pre.code .literal.number.hex { color: #2aa198 }                     /* Literal.Number.Hex */
pre.code .literal.number.integer { color: #2aa198 }                     /* Literal.Number.Integer */
pre.code .literal.number.integer.long { color: #2aa198 }                     /* Literal.Number.Integer.Long */
pre.code .literal.number.oct { color: #2aa198 }                     /* Literal.Number.Oct */

pre.code .operator { }                                     /* Operator */
pre.code .operator.word { color: #859900 }                     /* Operator.Word */

pre.code .punctiation { }                                     /* Punctuation */

pre.code .comment { color: #586e75; font-style: italic }  /* Comment */
pre.code .comment.multiline { color: #586e75; font-style: italic } /* Comment.Multiline */
pre.code .comment.preproc { color: #586e75; font-style: italic } /* Comment.Preproc */
pre.code .comment.single { color: #586e75; font-style: italic } /* Comment.Single */
pre.code .comment.special { color: #586e75; font-style: italic } /* Comment.Special */

pre.code .generic { }                                     /* Generic */
pre.code .other { }                                     /* Other */
pre.code .generic.deleted { }                                    /* Generic.Deleted */
pre.code .generic.emph { }                                    /* Generic.Emph */
pre.code .generic.error { }                                    /* Generic.Error */
pre.code .generic.heading { }                                    /* Generic.Heading */
pre.code .generic.inserted { }                                    /* Generic.Inserted */
pre.code .generic.output { }                                    /* Generic.Output */
pre.code .generic.prompt { }                                    /* Generic.Prompt */
pre.code .generic.strong { }                                    /* Generic.Strong */
pre.code .generic.subheading { }                                    /* Generic.Subheading */
pre.code .generic.traceback { }                                    /* Generic.Traceback */

/* Math */
/* styled separately (see math.css for math-output=HTML) */

/* Rubric */

p.rubric {
  font-weight: bold;
  font-size: larger;
  color: #b58900;
}

/* Epigraph */
/* Highlights */
/* Pull-Quote */
/* Compound Paragraph */
/* Container */

/* can be styled in a custom stylesheet */

/* Document Header &amp; Footer */

div.footer, div.header {
  clear: both;
}

hr.header {
  border: 1px solid;
  margin: 0;
  padding: 0;
}

hr.footer {
  border: 1px solid;
  margin: 0;
  padding: 0;
}

div.header {
  margin-top: 1em;
  margin-bottom: 4em;
}

div.footer {
  margin-top: 4em;
  margin-bottom: 1em;
}

/* Contents */

div.topic.contents {
  margin: 0; /* don't indent like a topic */
}

ul.auto-toc {
  list-style-type: none;
}

/* Inline Markup
*  =============
*/

* Emphasis */
/*   em  */
/* Strong Emphasis */
/*   strong  */
/* Interpreted Text */
/*   span.interpreted  	*/
/* Title Reference */
/*   cite   */
/* Inline Literals */

tt.literal, span.docutils.literal {
  font-family: Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  font-size: large;
  /*color: #ededed;
  background-color: #073642;
  border: 1px solid #586e75;*/
  /* possible values: normal, nowrap, pre, pre-wrap, pre-line */
  white-space: pre-wrap;
}

tt.docutils.literal {
  font-family: Courier, Droid Sans Mono, DejaVu Sans Mono, monospace;
  font-size: large;
  /*color: #ededed;
  background-color: #073642;*/
  border: none; /*1px solid #586e75;*/
  /* possible values: normal, nowrap, pre, pre-wrap, pre-line */
  white-space: pre-wrap;
}


/* do not wraph a hyphens and similar: */
.literal &gt; span.pre { white-space: nowrap; }

/* Hyperlink References */

a {
  text-decoration: none;
  color: #268bd2;
}

a:hover {
  text-decoration: underline;
}

a:visited {
  color: #6c71c4;
}

/* External Targets */
/*   span.target.external   */
/* Internal Targets */
/*   span.target.internal   */
/* Footnote References */
/*   a.footnote-reference   */
/* Citation References  */
/*   a.citation-reference   */


// ============= Begin pygment-specific additions =====================
pre { line-height: 125%; }
td.linenos .normal { color: #586e75; background-color: #073642; padding-left: 5px; padding-right: 5px; }
span.linenos { color: #586e75; background-color: #073642; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.hll { background-color: #073642 }
.c { color: #586e75; font-style: italic } /* Comment */
.err { color: #839496; background-color: #dc322f } /* Error */
.esc { color: #839496 } /* Escape */
.g { color: #839496 } /* Generic */
.k { color: #859900 } /* Keyword */
.l { color: #839496 } /* Literal */
.n { color: #839496 } /* Name */
.o { color: #586e75 } /* Operator */
.x { color: #839496 } /* Other */
.p { color: #839496 } /* Punctuation */
.ch { color: #586e75; font-style: italic } /* Comment.Hashbang */
.cm { color: #586e75; font-style: italic } /* Comment.Multiline */
.cp { color: #d33682 } /* Comment.Preproc */
.cpf { color: #586e75 } /* Comment.PreprocFile */
.c1 { color: #586e75; font-style: italic } /* Comment.Single */
.cs { color: #586e75; font-style: italic } /* Comment.Special */
.gd { color: #dc322f } /* Generic.Deleted */
.ge { color: #839496; font-style: italic } /* Generic.Emph */
.gr { color: #dc322f } /* Generic.Error */
.gh { color: #839496; font-weight: bold } /* Generic.Heading */
.gi { color: #859900 } /* Generic.Inserted */
.go { color: #839496 } /* Generic.Output */
.gp { color: #268bd2; font-weight: bold } /* Generic.Prompt */
.gs { color: #839496; font-weight: bold } /* Generic.Strong */
.gu { color: #839496; text-decoration: underline } /* Generic.Subheading */
.gt { color: #268bd2 } /* Generic.Traceback */
.kc { color: #2aa198 } /* Keyword.Constant */
.kd { color: #2aa198 } /* Keyword.Declaration */
.kn { color: #cb4b16 } /* Keyword.Namespace */
.kp { color: #859900 } /* Keyword.Pseudo */
.kr { color: #859900 } /* Keyword.Reserved */
.kt { color: #b58900 } /* Keyword.Type */
.ld { color: #839496 } /* Literal.Date */
.m { color: #2aa198 } /* Literal.Number */
.s { color: #2aa198 } /* Literal.String */
.na { color: #839496 } /* Name.Attribute */
.nb { color: #268bd2 } /* Name.Builtin */
.nc { color: #268bd2 } /* Name.Class */
.no { color: #268bd2 } /* Name.Constant */
.nd { color: #268bd2 } /* Name.Decorator */
.ni { color: #268bd2 } /* Name.Entity */
.ne { color: #268bd2 } /* Name.Exception */
.nf { color: #268bd2 } /* Name.Function */
.nl { color: #268bd2 } /* Name.Label */
.nn { color: #268bd2 } /* Name.Namespace */
.nx { color: #839496 } /* Name.Other */
.py { color: #839496 } /* Name.Property */
.nt { color: #268bd2 } /* Name.Tag */
.nv { color: #268bd2 } /* Name.Variable */
.ow { color: #859900 } /* Operator.Word */
.w { color: #839496 } /* Text.Whitespace */
.mb { color: #2aa198 } /* Literal.Number.Bin */
.mf { color: #2aa198 } /* Literal.Number.Float */
.mh { color: #2aa198 } /* Literal.Number.Hex */
.mi { color: #2aa198 } /* Literal.Number.Integer */
.mo { color: #2aa198 } /* Literal.Number.Oct */
.sa { color: #2aa198 } /* Literal.String.Affix */
.sb { color: #2aa198 } /* Literal.String.Backtick */
.sc { color: #2aa198 } /* Literal.String.Char */
.dl { color: #2aa198 } /* Literal.String.Delimiter */
.sd { color: #586e75 } /* Literal.String.Doc */
.s2 { color: #2aa198 } /* Literal.String.Double */
.se { color: #2aa198 } /* Literal.String.Escape */
.sh { color: #2aa198 } /* Literal.String.Heredoc */
.si { color: #2aa198 } /* Literal.String.Interpol */
.sx { color: #2aa198 } /* Literal.String.Other */
.sr { color: #cb4b16 } /* Literal.String.Regex */
.s1 { color: #2aa198 } /* Literal.String.Single */
.ss { color: #2aa198 } /* Literal.String.Symbol */
.bp { color: #268bd2 } /* Name.Builtin.Pseudo */
.fm { color: #268bd2 } /* Name.Function.Magic */
.vc { color: #268bd2 } /* Name.Variable.Class */
.vg { color: #268bd2 } /* Name.Variable.Global */
.vi { color: #268bd2 } /* Name.Variable.Instance */
.vm { color: #268bd2 } /* Name.Variable.Magic */
.il { color: #2aa198 } /* Literal.Number.Integer.Long */

@media print {
  footer {page-break-after: always;}
}

&lt;/style&gt;
&lt;style id="MJX-CHTML-styles"&gt;
mjx-container[jax="CHTML"] {
  line-height: 0;
}

mjx-container [space="1"] {
  margin-left: .111em;
}

mjx-container [space="2"] {
  margin-left: .167em;
}

mjx-container [space="3"] {
  margin-left: .222em;
}

mjx-container [space="4"] {
  margin-left: .278em;
}

mjx-container [space="5"] {
  margin-left: .333em;
}

mjx-container [rspace="1"] {
  margin-right: .111em;
}

mjx-container [rspace="2"] {
  margin-right: .167em;
}

mjx-container [rspace="3"] {
  margin-right: .222em;
}

mjx-container [rspace="4"] {
  margin-right: .278em;
}

mjx-container [rspace="5"] {
  margin-right: .333em;
}

mjx-container [size="s"] {
  font-size: 70.7%;
}

mjx-container [size="ss"] {
  font-size: 50%;
}

mjx-container [size="Tn"] {
  font-size: 60%;
}

mjx-container [size="sm"] {
  font-size: 85%;
}

mjx-container [size="lg"] {
  font-size: 120%;
}

mjx-container [size="Lg"] {
  font-size: 144%;
}

mjx-container [size="LG"] {
  font-size: 173%;
}

mjx-container [size="hg"] {
  font-size: 207%;
}

mjx-container [size="HG"] {
  font-size: 249%;
}

mjx-container [width="full"] {
  width: 100%;
}

mjx-box {
  display: inline-block;
}

mjx-block {
  display: block;
}

mjx-itable {
  display: inline-table;
}

mjx-row {
  display: table-row;
}

mjx-row &gt; * {
  display: table-cell;
}

mjx-mtext {
  display: inline-block;
}

mjx-mstyle {
  display: inline-block;
}

mjx-merror {
  display: inline-block;
  color: red;
  background-color: yellow;
}

mjx-mphantom {
  visibility: hidden;
}

_::-webkit-full-page-media, _:future, :root mjx-container {
  will-change: opacity;
}

mjx-assistive-mml {
  position: absolute !important;
  top: 0px;
  left: 0px;
  clip: rect(1px, 1px, 1px, 1px);
  padding: 1px 0px 0px 0px !important;
  border: 0px !important;
  display: block !important;
  width: auto !important;
  overflow: hidden !important;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

mjx-assistive-mml[display="block"] {
  width: 100% !important;
}

mjx-math {
  display: inline-block;
  text-align: left;
  line-height: 0;
  text-indent: 0;
  font-style: normal;
  font-weight: normal;
  font-size: 100%;
  font-size-adjust: none;
  letter-spacing: normal;
  word-wrap: normal;
  word-spacing: normal;
  white-space: nowrap;
  direction: ltr;
  padding: 1px 0;
}

mjx-container[jax="CHTML"][display="true"] {
  display: block;
  text-align: center;
  margin: 1em 0;
}

mjx-container[jax="CHTML"][display="true"][width="full"] {
  display: flex;
}

mjx-container[jax="CHTML"][display="true"] mjx-math {
  padding: 0;
}

mjx-container[jax="CHTML"][justify="left"] {
  text-align: left;
}

mjx-container[jax="CHTML"][justify="right"] {
  text-align: right;
}

mjx-mi {
  display: inline-block;
  text-align: left;
}

mjx-c {
  display: inline-block;
}

mjx-utext {
  display: inline-block;
  padding: .75em 0 .2em 0;
}

mjx-mo {
  display: inline-block;
  text-align: left;
}

mjx-stretchy-h {
  display: inline-table;
  width: 100%;
}

mjx-stretchy-h &gt; * {
  display: table-cell;
  width: 0;
}

mjx-stretchy-h &gt; * &gt; mjx-c {
  display: inline-block;
  transform: scalex(1.0000001);
}

mjx-stretchy-h &gt; * &gt; mjx-c::before {
  display: inline-block;
  width: initial;
}

mjx-stretchy-h &gt; mjx-ext {
  /* IE */ overflow: hidden;
  /* others */ overflow: clip visible;
  width: 100%;
}

mjx-stretchy-h &gt; mjx-ext &gt; mjx-c::before {
  transform: scalex(500);
}

mjx-stretchy-h &gt; mjx-ext &gt; mjx-c {
  width: 0;
}

mjx-stretchy-h &gt; mjx-beg &gt; mjx-c {
  margin-right: -.1em;
}

mjx-stretchy-h &gt; mjx-end &gt; mjx-c {
  margin-left: -.1em;
}

mjx-stretchy-v {
  display: inline-block;
}

mjx-stretchy-v &gt; * {
  display: block;
}

mjx-stretchy-v &gt; mjx-beg {
  height: 0;
}

mjx-stretchy-v &gt; mjx-end &gt; mjx-c {
  display: block;
}

mjx-stretchy-v &gt; * &gt; mjx-c {
  transform: scaley(1.0000001);
  transform-origin: left center;
  overflow: hidden;
}

mjx-stretchy-v &gt; mjx-ext {
  display: block;
  height: 100%;
  box-sizing: border-box;
  border: 0px solid transparent;
  /* IE */ overflow: hidden;
  /* others */ overflow: visible clip;
}

mjx-stretchy-v &gt; mjx-ext &gt; mjx-c::before {
  width: initial;
  box-sizing: border-box;
}

mjx-stretchy-v &gt; mjx-ext &gt; mjx-c {
  transform: scaleY(500) translateY(.075em);
  overflow: visible;
}

mjx-mark {
  display: inline-block;
  height: 0px;
}

mjx-mn {
  display: inline-block;
  text-align: left;
}

mjx-msup {
  display: inline-block;
  text-align: left;
}

mjx-mfrac {
  display: inline-block;
  text-align: left;
}

mjx-frac {
  display: inline-block;
  vertical-align: 0.17em;
  padding: 0 .22em;
}

mjx-frac[type="d"] {
  vertical-align: .04em;
}

mjx-frac[delims] {
  padding: 0 .1em;
}

mjx-frac[atop] {
  padding: 0 .12em;
}

mjx-frac[atop][delims] {
  padding: 0;
}

mjx-dtable {
  display: inline-table;
  width: 100%;
}

mjx-dtable &gt; * {
  font-size: 2000%;
}

mjx-dbox {
  display: block;
  font-size: 5%;
}

mjx-num {
  display: block;
  text-align: center;
}

mjx-den {
  display: block;
  text-align: center;
}

mjx-mfrac[bevelled] &gt; mjx-num {
  display: inline-block;
}

mjx-mfrac[bevelled] &gt; mjx-den {
  display: inline-block;
}

mjx-den[align="right"], mjx-num[align="right"] {
  text-align: right;
}

mjx-den[align="left"], mjx-num[align="left"] {
  text-align: left;
}

mjx-nstrut {
  display: inline-block;
  height: .054em;
  width: 0;
  vertical-align: -.054em;
}

mjx-nstrut[type="d"] {
  height: .217em;
  vertical-align: -.217em;
}

mjx-dstrut {
  display: inline-block;
  height: .505em;
  width: 0;
}

mjx-dstrut[type="d"] {
  height: .726em;
}

mjx-line {
  display: block;
  box-sizing: border-box;
  min-height: 1px;
  height: .06em;
  border-top: .06em solid;
  margin: .06em -.1em;
  overflow: hidden;
}

mjx-line[type="d"] {
  margin: .18em -.1em;
}

mjx-mrow {
  display: inline-block;
  text-align: left;
}

mjx-msqrt {
  display: inline-block;
  text-align: left;
}

mjx-root {
  display: inline-block;
  white-space: nowrap;
}

mjx-surd {
  display: inline-block;
  vertical-align: top;
}

mjx-sqrt {
  display: inline-block;
  padding-top: .07em;
}

mjx-sqrt &gt; mjx-box {
  border-top: .07em solid;
}

mjx-sqrt.mjx-tall &gt; mjx-box {
  padding-left: .3em;
  margin-left: -.3em;
}

mjx-munderover {
  display: inline-block;
  text-align: left;
}

mjx-munderover:not([limits="false"]) {
  padding-top: .1em;
}

mjx-munderover:not([limits="false"]) &gt; * {
  display: block;
}

mjx-msubsup {
  display: inline-block;
  text-align: left;
}

mjx-script {
  display: inline-block;
  padding-right: .05em;
  padding-left: .033em;
}

mjx-script &gt; * {
  display: block;
}

mjx-TeXAtom {
  display: inline-block;
  text-align: left;
}

mjx-msub {
  display: inline-block;
  text-align: left;
}

mjx-c::before {
  display: block;
  width: 0;
}

.MJX-TEX {
  font-family: MJXZERO, MJXTEX;
}

.TEX-B {
  font-family: MJXZERO, MJXTEX-B;
}

.TEX-I {
  font-family: MJXZERO, MJXTEX-I;
}

.TEX-MI {
  font-family: MJXZERO, MJXTEX-MI;
}

.TEX-BI {
  font-family: MJXZERO, MJXTEX-BI;
}

.TEX-S1 {
  font-family: MJXZERO, MJXTEX-S1;
}

.TEX-S2 {
  font-family: MJXZERO, MJXTEX-S2;
}

.TEX-S3 {
  font-family: MJXZERO, MJXTEX-S3;
}

.TEX-S4 {
  font-family: MJXZERO, MJXTEX-S4;
}

.TEX-A {
  font-family: MJXZERO, MJXTEX-A;
}

.TEX-C {
  font-family: MJXZERO, MJXTEX-C;
}

.TEX-CB {
  font-family: MJXZERO, MJXTEX-CB;
}

.TEX-FR {
  font-family: MJXZERO, MJXTEX-FR;
}

.TEX-FRB {
  font-family: MJXZERO, MJXTEX-FRB;
}

.TEX-SS {
  font-family: MJXZERO, MJXTEX-SS;
}

.TEX-SSB {
  font-family: MJXZERO, MJXTEX-SSB;
}

.TEX-SSI {
  font-family: MJXZERO, MJXTEX-SSI;
}

.TEX-SC {
  font-family: MJXZERO, MJXTEX-SC;
}

.TEX-T {
  font-family: MJXZERO, MJXTEX-T;
}

.TEX-V {
  font-family: MJXZERO, MJXTEX-V;
}

.TEX-VB {
  font-family: MJXZERO, MJXTEX-VB;
}

mjx-stretchy-v mjx-c, mjx-stretchy-h mjx-c {
  font-family: MJXZERO, MJXTEX-S1, MJXTEX-S4, MJXTEX, MJXTEX-A ! important;
}

@font-face /* 0 */ {
  font-family: MJXZERO;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Zero.woff") format("woff");
}

@font-face /* 1 */ {
  font-family: MJXTEX;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff") format("woff");
}

@font-face /* 2 */ {
  font-family: MJXTEX-B;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff") format("woff");
}

@font-face /* 3 */ {
  font-family: MJXTEX-I;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff") format("woff");
}

@font-face /* 4 */ {
  font-family: MJXTEX-MI;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff") format("woff");
}

@font-face /* 5 */ {
  font-family: MJXTEX-BI;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff") format("woff");
}

@font-face /* 6 */ {
  font-family: MJXTEX-S1;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff") format("woff");
}

@font-face /* 7 */ {
  font-family: MJXTEX-S2;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff") format("woff");
}

@font-face /* 8 */ {
  font-family: MJXTEX-S3;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff") format("woff");
}

@font-face /* 9 */ {
  font-family: MJXTEX-S4;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff") format("woff");
}

@font-face /* 10 */ {
  font-family: MJXTEX-A;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff") format("woff");
}

@font-face /* 11 */ {
  font-family: MJXTEX-C;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff") format("woff");
}

@font-face /* 12 */ {
  font-family: MJXTEX-CB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff") format("woff");
}

@font-face /* 13 */ {
  font-family: MJXTEX-FR;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff") format("woff");
}

@font-face /* 14 */ {
  font-family: MJXTEX-FRB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff") format("woff");
}

@font-face /* 15 */ {
  font-family: MJXTEX-SS;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff") format("woff");
}

@font-face /* 16 */ {
  font-family: MJXTEX-SSB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff") format("woff");
}

@font-face /* 17 */ {
  font-family: MJXTEX-SSI;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff") format("woff");
}

@font-face /* 18 */ {
  font-family: MJXTEX-SC;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff") format("woff");
}

@font-face /* 19 */ {
  font-family: MJXTEX-T;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff") format("woff");
}

@font-face /* 20 */ {
  font-family: MJXTEX-V;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff") format("woff");
}

@font-face /* 21 */ {
  font-family: MJXTEX-VB;
  src: url("https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff") format("woff");
}

mjx-c.mjx-c1D44E.TEX-I::before {
  padding: 0.441em 0.529em 0.01em 0;
  content: "a";
}

mjx-c.mjx-c2212::before {
  padding: 0.583em 0.778em 0.082em 0;
  content: "\2212";
}

mjx-c.mjx-c32::before {
  padding: 0.666em 0.5em 0 0;
  content: "2";
}

mjx-c.mjx-c1D44F.TEX-I::before {
  padding: 0.694em 0.429em 0.011em 0;
  content: "b";
}

mjx-c.mjx-cB1::before {
  padding: 0.666em 0.778em 0 0;
  content: "\B1";
}

mjx-c.mjx-c221A::before {
  padding: 0.8em 0.853em 0.2em 0;
  content: "\221A";
}

mjx-c.mjx-c37::before {
  padding: 0.676em 0.5em 0.022em 0;
  content: "7";
}

mjx-c.mjx-c33::before {
  padding: 0.665em 0.5em 0.022em 0;
  content: "3";
}

mjx-c.mjx-c221E::before {
  padding: 0.442em 1em 0.011em 0;
  content: "\221E";
}

mjx-c.mjx-c2211.TEX-S2::before {
  padding: 0.95em 1.444em 0.45em 0;
  content: "\2211";
}

mjx-c.mjx-c1D45B.TEX-I::before {
  padding: 0.442em 0.6em 0.011em 0;
  content: "n";
}

mjx-c.mjx-c3D::before {
  padding: 0.583em 0.778em 0.082em 0;
  content: "=";
}

mjx-c.mjx-c31::before {
  padding: 0.666em 0.5em 0 0;
  content: "1";
}

mjx-c.mjx-c1D452.TEX-I::before {
  padding: 0.442em 0.466em 0.011em 0;
  content: "e";
}

mjx-c.mjx-c1D456.TEX-I::before {
  padding: 0.661em 0.345em 0.011em 0;
  content: "i";
}

mjx-c.mjx-c1D70B.TEX-I::before {
  padding: 0.431em 0.57em 0.011em 0;
  content: "\3C0";
}

mjx-c.mjx-c2B::before {
  padding: 0.583em 0.778em 0.082em 0;
  content: "+";
}

mjx-c.mjx-c28::before {
  padding: 0.75em 0.389em 0.25em 0;
  content: "(";
}

mjx-c.mjx-c35::before {
  padding: 0.666em 0.5em 0.022em 0;
  content: "5";
}

mjx-c.mjx-c1D465.TEX-I::before {
  padding: 0.442em 0.572em 0.011em 0;
  content: "x";
}

mjx-c.mjx-c34::before {
  padding: 0.677em 0.5em 0 0;
  content: "4";
}

mjx-c.mjx-c29::before {
  padding: 0.75em 0.389em 0.25em 0;
  content: ")";
}

mjx-c.mjx-c1D6FC.TEX-I::before {
  padding: 0.442em 0.64em 0.011em 0;
  content: "\3B1";
}

mjx-c.mjx-c1D461.TEX-I::before {
  padding: 0.626em 0.361em 0.011em 0;
  content: "t";
}

mjx-c.mjx-c1D443.TEX-I::before {
  padding: 0.683em 0.751em 0 0;
  content: "P";
}

mjx-c.mjx-c1D442.TEX-I::before {
  padding: 0.704em 0.763em 0.022em 0;
  content: "O";
}

mjx-c.mjx-c2C::before {
  padding: 0.121em 0.278em 0.194em 0;
  content: ",";
}

mjx-c.mjx-c2026::before {
  padding: 0.12em 1.172em 0 0;
  content: "\2026";
}

mjx-c.mjx-c1D45E.TEX-I::before {
  padding: 0.442em 0.46em 0.194em 0;
  content: "q";
}

mjx-c.mjx-c1D446.TEX-I::before {
  padding: 0.705em 0.645em 0.022em 0;
  content: "S";
}

mjx-c.mjx-c1D706.TEX-I::before {
  padding: 0.694em 0.583em 0.012em 0;
  content: "\3BB";
}

mjx-c.mjx-c1D458.TEX-I::before {
  padding: 0.694em 0.521em 0.011em 0;
  content: "k";
}

mjx-c.mjx-c222B.TEX-S2::before {
  padding: 1.36em 0.944em 0.862em 0;
  content: "\222B";
}

mjx-c.mjx-c30::before {
  padding: 0.666em 0.5em 0.022em 0;
  content: "0";
}

mjx-c.mjx-c1D451.TEX-I::before {
  padding: 0.694em 0.52em 0.01em 0;
  content: "d";
}

mjx-c.mjx-c1D438.TEX-I::before {
  padding: 0.68em 0.764em 0 0;
  content: "E";
}

mjx-c.mjx-c1D45A.TEX-I::before {
  padding: 0.442em 0.878em 0.011em 0;
  content: "m";
}

mjx-c.mjx-c1D450.TEX-I::before {
  padding: 0.442em 0.433em 0.011em 0;
  content: "c";
}
&lt;/style&gt;&lt;/head&gt;
&lt;body&gt;
&lt;div class="document" id="formula"&gt;
&lt;h1 class="title"&gt;Formula&lt;/h1&gt;

&lt;p&gt;These should be some in line math &lt;span class="math"&gt;&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" tabindex="0" ctxtmenu_counter="0" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math class="MJX-TEX" aria-hidden="true"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D44E TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c2212"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mn class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D44F TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.363em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="inline"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mo&gt;âˆ’&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;&lt;/span&gt; here.&lt;/p&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="1" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-mfrac&gt;&lt;mjx-frac type="d"&gt;&lt;mjx-num&gt;&lt;mjx-nstrut type="d"&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mrow&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-cB1"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msqrt space="3"&gt;&lt;mjx-sqrt&gt;&lt;mjx-surd&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c221A"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;/mjx-surd&gt;&lt;mjx-box style="padding-top: 0.158em;"&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c37"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-box&gt;&lt;/mjx-sqrt&gt;&lt;/mjx-msqrt&gt;&lt;/mjx-mrow&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line type="d"&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut type="d"&gt;&lt;/mjx-dstrut&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c33"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mo&gt;Â±&lt;/mo&gt;&lt;msqrt&gt;&lt;mn&gt;7&lt;/mn&gt;&lt;/msqrt&gt;&lt;/mrow&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;/mfrac&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;p&gt;Some text here, and then some more math:&lt;/p&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="2" style="font-size: 123.1%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-munderover&gt;&lt;mjx-over style="padding-bottom: 0.192em; padding-left: 0.368em;"&gt;&lt;mjx-mi class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c221E"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-over&gt;&lt;mjx-box&gt;&lt;mjx-munder&gt;&lt;mjx-row&gt;&lt;mjx-base&gt;&lt;mjx-mo class="mjx-lop"&gt;&lt;mjx-c class="mjx-c2211 TEX-S2"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;/mjx-base&gt;&lt;/mjx-row&gt;&lt;mjx-row&gt;&lt;mjx-under style="padding-top: 0.167em; padding-left: 0.058em;"&gt;&lt;mjx-texatom size="s" texclass="ORD"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D45B TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-texatom&gt;&lt;/mjx-under&gt;&lt;/mjx-row&gt;&lt;/mjx-munder&gt;&lt;/mjx-box&gt;&lt;/mjx-munderover&gt;&lt;mjx-mfrac space="2"&gt;&lt;mjx-frac type="d"&gt;&lt;mjx-num&gt;&lt;mjx-nstrut type="d"&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mrow&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2212"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D452 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.363em;"&gt;&lt;mjx-texatom size="s" texclass="ORD"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D456 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D70B TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-texatom&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-mrow&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line type="d"&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut type="d"&gt;&lt;/mjx-dstrut&gt;&lt;mjx-msup&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-script style="vertical-align: 0.289em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D45B TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c2B"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c28"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mfrac&gt;&lt;mjx-frac type="d"&gt;&lt;mjx-num&gt;&lt;mjx-nstrut type="d"&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mrow&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c35"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;mjx-mo class="mjx-n" space="3"&gt;&lt;mjx-c class="mjx-c2212"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mfrac space="3"&gt;&lt;mjx-frac&gt;&lt;mjx-num&gt;&lt;mjx-nstrut&gt;&lt;/mjx-nstrut&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut&gt;&lt;/mjx-dstrut&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;/mjx-mrow&gt;&lt;/mjx-num&gt;&lt;mjx-dbox&gt;&lt;mjx-dtable&gt;&lt;mjx-line type="d"&gt;&lt;/mjx-line&gt;&lt;mjx-row&gt;&lt;mjx-den&gt;&lt;mjx-dstrut type="d"&gt;&lt;/mjx-dstrut&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c34"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-den&gt;&lt;/mjx-row&gt;&lt;/mjx-dtable&gt;&lt;/mjx-dbox&gt;&lt;/mjx-frac&gt;&lt;/mjx-mfrac&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c29"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;munderover&gt;&lt;mo data-mjx-texclass="OP"&gt;âˆ‘&lt;/mo&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;mi mathvariant="normal"&gt;âˆž&lt;/mi&gt;&lt;/munderover&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mo&gt;âˆ’&lt;/mo&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;Ï€&lt;/mi&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msup&gt;&lt;/mfrac&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mn&gt;5&lt;/mn&gt;&lt;mo&gt;âˆ’&lt;/mo&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;mn&gt;4&lt;/mn&gt;&lt;/mfrac&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;p&gt;To get this to work right with rst2pdf, we need to put a blank line between each line of math.&lt;/p&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="3" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-msub&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D6FC TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D461 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c28"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D456 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c29"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mi class="mjx-i" space="4"&gt;&lt;mjx-c class="mjx-c1D443 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c28"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D442 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2C"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="2"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D442 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2C"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="2"&gt;&lt;mjx-c class="mjx-c2026"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="2"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D442 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D461 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c2C"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="2"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D45E TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em; margin-left: -0.014em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D461 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mo class="mjx-n"&gt;&lt;mjx-c class="mjx-c29"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msub space="4"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D446 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: -0.15em; margin-left: -0.032em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D456 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msub&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D706 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;msub&gt;&lt;mi&gt;Î±&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;P&lt;/mi&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;mo&gt;â€¦&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;q&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;S&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mi&gt;Î»&lt;/mi&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="4" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D458 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-msubsup space="4"&gt;&lt;mjx-mo class="mjx-lop"&gt;&lt;mjx-c class="mjx-c222B TEX-S2"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-script style="vertical-align: -0.896em; margin-left: -0.388em;"&gt;&lt;mjx-texatom size="s" texclass="ORD" style="margin-left: 0.647em;"&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c31"&gt;&lt;/mjx-c&gt;&lt;mjx-c class="mjx-c30"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-texatom&gt;&lt;mjx-spacer style="margin-top: 1.498em;"&gt;&lt;/mjx-spacer&gt;&lt;mjx-texatom size="s" texclass="ORD"&gt;&lt;mjx-mn class="mjx-n"&gt;&lt;mjx-c class="mjx-c30"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-texatom&gt;&lt;/mjx-script&gt;&lt;/mjx-msubsup&gt;&lt;mjx-mi class="mjx-i" space="2"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D451 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;msubsup&gt;&lt;mo data-mjx-texclass="OP"&gt;âˆ«&lt;/mo&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;mrow data-mjx-texclass="ORD"&gt;&lt;mn&gt;10&lt;/mn&gt;&lt;/mrow&gt;&lt;/msubsup&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;div class="math"&gt;
&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" display="true" tabindex="0" ctxtmenu_counter="5" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math display="true" class="MJX-TEX" aria-hidden="true" style="margin-left: 0px; margin-right: 0px;"&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D438 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-mo class="mjx-n" space="4"&gt;&lt;mjx-c class="mjx-c3D"&gt;&lt;/mjx-c&gt;&lt;/mjx-mo&gt;&lt;mjx-mi class="mjx-i" space="4"&gt;&lt;mjx-c class="mjx-c1D45A TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D450 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.413em;"&gt;&lt;mjx-mn class="mjx-n" size="s"&gt;&lt;mjx-c class="mjx-c32"&gt;&lt;/mjx-c&gt;&lt;/mjx-mn&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="block"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;msup&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
&lt;/div&gt;
&lt;p&gt;More inline math: &lt;span class="math"&gt;&lt;mjx-container class="MathJax CtxtMenu_Attached_0" jax="CHTML" tabindex="0" ctxtmenu_counter="6" style="font-size: 123.4%; position: relative;"&gt;&lt;mjx-math class="MJX-TEX" aria-hidden="true"&gt;&lt;mjx-msup&gt;&lt;mjx-mi class="mjx-i"&gt;&lt;mjx-c class="mjx-c1D452 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;mjx-script style="vertical-align: 0.363em;"&gt;&lt;mjx-mi class="mjx-i" size="s"&gt;&lt;mjx-c class="mjx-c1D465 TEX-I"&gt;&lt;/mjx-c&gt;&lt;/mjx-mi&gt;&lt;/mjx-script&gt;&lt;/mjx-msup&gt;&lt;/mjx-math&gt;&lt;mjx-assistive-mml unselectable="on" display="inline"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;


&lt;/body&gt;&lt;span style="--colorFg:#d3d9e3; --colorFgAlpha:#d3d9e31a; --colorFgIntense:#f9ffff; --colorFgFaded:#c9ced8; --colorFgFadedMore:#bec3cd; --colorFgFadedMost:#a6abb3; --colorBg:#2e2f37; --colorBgAlpha:#2e2f37e6; --colorBgAlphaHeavy:#2e2f37a6; --colorBgAlphaHeavier:#2e2f3740; --colorBgAlphaBlur:#2e2f37f2; --colorBgDark:#282931; --colorBgDarker:#23242b; --colorBgLight:#32333b; --colorBgLighter:#42434b; --colorBgLightIntense:#36373f; --colorBgIntense:#23242b; --colorBgIntenser:#1b1c24; --colorBgIntserAlpha:#181920eb; --colorBgInverse:#32333b; --colorBgInverser:#3e3f47; --colorBgFaded:#3a3b43; --backgroundBlur:blur(10px); --colorHighlightBg:#6590fd; --colorHighlightBgUnfocused:#565860; --colorHighlightBgAlpha:#6590fd1a; --colorHighlightBgDark:#4779e3; --colorHighlightFg:#ffffff; --colorHighlightFgAlpha:#ffffff80; --colorHighlightFgAlphaHeavy:#ffffff40; --colorAccentBg:#44454d; --colorAccentBgAlpha:#78798266; --colorAccentBgAlphaHeavy:#78798240; --colorAccentBgDark:#34353d; --colorAccentBgDarker:#1d1e25; --colorAccentBgFaded:#393939; --colorAccentBgFadedMore:#595a62; --colorAccentBgFadedMost:#787982; --colorAccentBorder:#34353d; --colorAccentBorderDark:#2a2b33; --colorAccentFg:#ffffff; --colorAccentFgFaded:#cacacb; --colorAccentFgAlpha:#ffffff40; --colorBorder:#1b1c24; --colorBorderDisabled:#23242b; --colorBorderSubtle:#212229; --colorBorderIntense:#0b0c15; --colorSuccessBg:#06a700; --colorSuccessBgAlpha:#06a7001a; --colorSuccessFg:#ffffff; --colorWarningBg:#efaf00; --colorWarningBgAlpha:#efaf001a; --colorWarningFg:#000000; --colorErrorBg:#c64539; --colorErrorBgAlpha:#c645391a; --colorErrorFg:#ffffff;"&gt;&lt;/span&gt;&lt;/html&gt;</t>
<t tx="ekr.20251214045245.2">@language python
g.cls()
print ('Hi')
# Trailing ws follows.
&lt;&lt; section 1 &gt;&gt;   
if (
    &lt;&lt; test &gt;&gt;
):
    pass
# Trailing ws follows.
if 1:  
    @others   # blah blah
</t>
<t tx="ekr.20251214045245.20">@language python
@nosearch
# changed
</t>
<t tx="ekr.20251214045245.21"></t>
<t tx="ekr.20251214045245.22">@language html
&lt;a href="http://127.0.0.1:8000/leo_toc.html"&gt;Link&lt;/a&gt;</t>
<t tx="ekr.20251214045245.23">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;style&gt;
ul.leo-tree-example  {
    background-color: #ffffec;
    zoom: 150%; # blurs icons a bit.
}
ul.leo-tree-example li {
    background-repeat: no-repeat;
    background-position: 0px 5px;
    padding-left: 27px;
}
li {
    background-image:
url('https://raw.github.com/vivainio/leo/master/leo/Icons/box00.GIF');
    background-repeat: no-repeat;
    background-position: 0px 5px;
    padding-left: 27px;
}
li.selected {
    background-color: lightgrey;
}
li.leaf {
    list-style-type: none;
}
li.plus {
  list-style-image: url('http://leoeditor.com/plusnode.gif')
}
li.minus {
  list-style-image: url('http://leoeditor.com/minusnode.gif')
}
li.leaf {
  background-image: url('http://leoeditor.com/box00.GIF')
}
li.body {
  background-image: url('http://leoeditor.com/box01.GIF')
}
li.mark {
  background-image: url('http://leoeditor.com/box02.GIF')
}
li.mark-body {
  background-image: url('http://leoeditor.com/box03.GIF')
}
li.clone {
  background-image: url('http://leoeditor.com/box04.GIF')
}
li.clone-body {
  background-image: url('http://leoeditor.com/box05.GIF')
}
li.clone-mark {
  background-image: url('http://leoeditor.com/box06.GIF')
}
li.clone-mark-body {
  background-image: url('http://leoeditor.com/box07.GIF')
}
li.dirty {
  background-image: url('http://leoeditor.com/box08.GIF')
}
li.dirty-body {
  background-image: url('http://leoeditor.com/box09.GIF')
}
li.dirty-mark {
  background-image: url('http://leoeditor.com/box10.GIF')
}
li.dirty-mark-body {
  background-image: url('http://leoeditor.com/box11.GIF')
}
li.dirty-clone {
  background-image: url('http://leoeditor.com/box12.GIF')
}
li.dirty-clone-body {
  background-image: url('http://leoeditor.com/box13.GIF')
}
li.dirty-clone-mark {
  background-image: url('http://leoeditor.com/box14.GIF')
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;ul class="leo-tree-example"&gt;
&lt;li class='plus clone-mark'&gt;
test
&lt;/li&gt;
&lt;ul&gt;
  &lt;li class='plus clone-body'&gt;
  child
  &lt;/li&gt;
  &lt;ul&gt;
    &lt;li class='leaf body'&gt;
    grandchild
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;li class='plus clone-body'&gt;
  child
  &lt;/li&gt;
  &lt;ul&gt;
    &lt;li class='leaf body'&gt;
    grandchild
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;</t>
<t tx="ekr.20251214045245.24">@language html

&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;!--@+leo-ver=5-thin--&gt;
&lt;!--@+node:ekr.20100808060203.4273: * @thin html/front.html--&gt;
&lt;!--@@first--&gt;
&lt;!--@@first--&gt;

&lt;!--@@language html--&gt;
&lt;!--@@tabwidth -2--&gt;

&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;
&lt;head&gt;
    &lt;!--@+&lt;&lt; head &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4274: ** &lt;&lt; head &gt;&gt;--&gt;
    &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;

    &lt;title&gt;Leo's Home Page&lt;/title&gt;
    &lt;link rel="stylesheet" href="_static/default.css" type="text/css" /&gt;
    &lt;link rel="stylesheet" href="_static/silver_city.css" type="text/css" /&gt;
    &lt;!--
    &lt;script type="text/javascript"&gt;
      var DOCUMENTATION_OPTIONS = {
        URL_ROOT:    '',
        VERSION:     '4.7-final',
        COLLAPSE_MODINDEX: false,
        FILE_SUFFIX: '.html',
        HAS_SOURCE:  false
      };
    &lt;/script&gt;
    --&gt;
    &lt;!-- &lt;script type="text/javascript" src="_static/jquery.js"&gt;&lt;/script&gt;--&gt;
    &lt;!-- &lt;script type="text/javascript" src="_static/doctools.js"&gt;&lt;/script&gt;--&gt;
    &lt;link rel="top" title="Leo v4.7-final documentation" href="index.html" /&gt;
    &lt;link rel="Users Guide" title="Leo&amp;#8217;s Users Guide" href="leo_toc.html" /&gt;
    &lt;!-- &lt;link rel="prev" title="Chapter 5: Using LeoÃ¢â‚¬â„¢s Commands" href="commands.html" /&gt;--&gt; 
    &lt;!--@-&lt;&lt; head &gt;&gt;--&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;!--@+&lt;&lt; div top nav &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4276: ** &lt;&lt; div top nav &gt;&gt;--&gt;
    &lt;div class="related"&gt;
      &lt;h3&gt;Navigation&lt;/h3&gt;
      &lt;ul&gt;
        &lt;li class="right" style="margin-right: 10px"&gt;
          &lt;a href="genindex.html" title="General Index"
             accesskey="I"&gt;index&lt;/a&gt;&lt;/li&gt;
        &lt;li class="right" &gt;
          &lt;a href="leo_toc.html" title="Leo&amp;#8217;s Users Guide"
             accesskey="N"&gt;contents&lt;/a&gt; |&lt;/li&gt;
        &lt;!--
        &lt;li class="right" &gt;
          &lt;a href="commands.html" title="Chapter 5: Using Leo&amp;#8217;s Commands"
             accesskey="P"&gt;previous&lt;/a&gt; |&lt;/li&gt;
        --&gt;
        &lt;li&gt;&lt;a href="leo_toc.html"&gt;Leo v4.7-final documentation&lt;/a&gt; &amp;raquo;&lt;/li&gt;

      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!--@-&lt;&lt; div top nav &gt;&gt;--&gt;
    &lt;div class="document"&gt;
      &lt;div class="documentwrapper"&gt;
        &lt;div class="bodywrapper"&gt;
          &lt;div class="body"&gt;
            &lt;!--@+&lt;&lt; div outer section &gt;&gt;--&gt;
            &lt;!--@+node:ekr.20100808060203.4286: ** &lt;&lt; div outer section &gt;&gt;--&gt;
            &lt;div class="section" id="Leo&amp;#8217;s Home page"&gt;

                &lt;h1&gt;Leo&amp;#8217;s Home Page&lt;/h1&gt;

                Leo is...

                &lt;!--@+&lt;&lt; what is Leo list &gt;&gt;--&gt;
                &lt;!--@+node:ekr.20100808060203.4288: *3* &lt;&lt; what is Leo list &gt;&gt;--&gt;
                &lt;li&gt;
                    An outline-oriented &lt;i&gt;browser&lt;/i&gt; and &lt;i&gt;project manager&lt;/i&gt; that organizes&lt;br&gt;
                    programs, web sites, URL's, pictures, movies or any other kind of data.&lt;br&gt;
                    You can organize your data in as many ways as you like within a &lt;i&gt;single&lt;/i&gt; outline.&lt;/li&gt;
                &lt;li&gt;
                    A &lt;i&gt;programmer's editor&lt;/i&gt; that fully integrates outline structure with programming.
                    &lt;!--
                    &lt;a HREF="http://www.eecs.harvard.edu/~nr/noweb/"&gt;noweb&lt;/a&gt; and
                    &lt;a HREF="http://www-cs-faculty.stanford.edu/~knuth/cweb.html"&gt;CWEB&lt;/a&gt; markup.&lt;/li&gt;
                    --&gt;
                &lt;li&gt;
                    &lt;i&gt;Fully scriptable&lt;/i&gt; using &lt;a HREF="http://www.python.org/"&gt;Python&lt;/a&gt;.
                    Leo's core is 100% pure Python.&lt;/li&gt;
                &lt;li&gt;
                    &lt;i&gt;Portable&lt;/i&gt;. Leo runs on Windows, Linux and MacOS X.&lt;br&gt;
                    Leo requires either the
                    &lt;a HREF="http://docs.python.org/library/tkinter.html"&gt;Tkinter&lt;/a&gt; or
                    &lt;a HREF="http://www.riverbankcomputing.co.uk/software/pyqt/intro"&gt;PyQt&lt;/a&gt; widget set.&lt;br&gt;
                    Leo's outline files are &lt;a HREF="http://www.w3.org/XML/"&gt;XML&lt;/a&gt; format.&lt;/li&gt;
                &lt;li&gt;
                    &lt;a href="http://www.opensource.org/"&gt;&lt;i&gt;Open Software&lt;/i&gt;&lt;/a&gt;,
                    distributed under the
                    &lt;a href="http://www.opensource.org/licenses/mit-license.php/"&gt;MIT License&lt;/a&gt;.&lt;/li&gt;
                &lt;!--@-&lt;&lt; what is Leo list &gt;&gt;--&gt;
                &lt;!-- div body toc --&gt;
                &lt;!-- &lt; &lt; inner sections &gt; &gt; --&gt;

                &lt;p&gt;Leo has an active community of helpful users and developers.&lt;br&gt;
                Please use the &lt;a href="http://groups.google.com/group/leo-editor"&gt;leo-editor&lt;/a&gt;
                group to ask questions and make suggestions.&lt;/p&gt;

                &lt;p&gt;Leo's author is &lt;a HREF="ekr.html"&gt;Edward K. Ream&lt;/a&gt;&lt;/p&gt;

            &lt;/div&gt;
            &lt;!--@-&lt;&lt; div outer section &gt;&gt;--&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;!--@+&lt;&lt; sidebar &gt;&gt;--&gt;
      &lt;!--@+node:ekr.20100808060203.4282: ** &lt;&lt; sidebar &gt;&gt;--&gt;
      &lt;div class="sphinxsidebar"&gt;
          &lt;div class="sphinxsidebarwrapper"&gt;
              &lt;!--@+&lt;&lt; sidebar contents &gt;&gt;--&gt;
              &lt;!--@+node:ekr.20100808060203.4283: *3* &lt;&lt; sidebar contents &gt;&gt;--&gt;
              &lt;p class="logo"&gt;&lt;a href="leo_toc.html"&gt;
                &lt;img class="logo" src="_static/Leo4-80-border.jpg" alt="Logo"/&gt;&lt;/a&gt;&lt;/p&gt;

              &lt;a class="reference external" href="intro.html"&gt;
                Read this tutorial first&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm"&gt;
                Another tutorial&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http:screen-shots.html"&gt;
                Screen shots of Leo&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="testimonials.html"&gt;
                Quotes from Leo&amp;#8217;s Users&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external"
                href="http://sourceforge.net/project/showfiles.php?group_id=3458&amp;amp;package_id=29106"&gt;
                Download Leo&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://groups.google.com/group/leo-editor"&gt;
                leo-editor: Google Groups&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://launchpad.net/leo-editor"&gt;
                leo-editor: Launchpad&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://leo.zwiki.org"&gt;
                Leo&amp;#8217;s Wiki&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="http://www.mind-mapping.org/"&gt;
                Mind Mapping&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="leoLinks.html"&gt;
                More links...&lt;/a&gt;&lt;br&gt;

              &lt;!-- &lt;a class="reference external"  href="install.html"&gt;Installing Leo&lt;/a&gt;&lt;br&gt;--&gt; 
              &lt;!-- &lt;a class="reference external"  href="intro.html"&gt;Beginners Guide&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!-- &lt;a class="reference external"  href="leo_toc.html"&gt;Users Guide&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!-- &lt;a class="reference external"  href="FAQ.html"&gt;FAQ&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!--Leo&amp;#8217;s &lt;a class="reference external" href="http://www.greygreen.org/leo/"&gt;Daily snapshots&lt;/a&gt;&lt;br&gt;--&gt;
              &lt;!-- 
              &lt;h5&gt;Edward K. Ream&lt;/h5&gt;
              &lt;a class="reference external" href="ekr.html"&gt;Home&lt;/a&gt;&lt;br&gt;
              &lt;a class="reference external" href="mailto:edreamleo@gmail.com"&gt;Contact&lt;/a&gt;&lt;br&gt;
              --&gt; 
              &lt;!--@-&lt;&lt; sidebar contents &gt;&gt;--&gt;
          &lt;!-- &lt; &lt; sidebar links &gt; &gt; --&gt;
          &lt;!-- &lt; &lt; searchbox &gt; &gt; --&gt;
          &lt;/div&gt;
      &lt;/div&gt;
      &lt;!--@-&lt;&lt; sidebar &gt;&gt;--&gt;
      &lt;div class="clearer"&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;!--@+&lt;&lt; div bottom nav &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4277: ** &lt;&lt; div bottom nav &gt;&gt;--&gt;
    &lt;div class="related"&gt;
      &lt;h3&gt;Navigation&lt;/h3&gt;
      &lt;ul&gt;
        &lt;li class="right" style="margin-right: 10px"&gt;
          &lt;a href="genindex.html" title="General Index"
             &gt;index&lt;/a&gt;&lt;/li&gt;
        &lt;li class="right" &gt;
          &lt;a href="leo_toc.html" title="Leo&amp;#8217;s Users Guide"
             &gt;contents&lt;/a&gt; |&lt;/li&gt;
        &lt;!--
        &lt;li class="right" &gt;
          &lt;a href="commands.html" title="Chapter 5: Using Leo&amp;#8217;s Commands"
             &gt;previous&lt;/a&gt; |&lt;/li&gt;
        --&gt;
        &lt;li&gt;&lt;a href="leo_toc.html"&gt;Leo v4.7-final documentation&lt;/a&gt; &amp;raquo;&lt;/li&gt; 
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!--@-&lt;&lt; div bottom nav &gt;&gt;--&gt;
    &lt;!--@+&lt;&lt; div footer &gt;&gt;--&gt;
    &lt;!--@+node:ekr.20100808060203.4278: ** &lt;&lt; div footer &gt;&gt;--&gt;
    &lt;div class="footer"&gt;
      &amp;copy; Copyright 2010, Edward K. Ream.
      Last updated on Aug 08, 2010.
      Created using &lt;a href="http://sphinx.pocoo.org/"&gt;Sphinx&lt;/a&gt; 0.6.3.
    &lt;/div&gt;
    &lt;!--@-&lt;&lt; div footer &gt;&gt;--&gt;
  &lt;/body&gt;
&lt;/html&gt;
&lt;!--@-leo--&gt;
</t>
<t tx="ekr.20251214045245.25"></t>
<t tx="ekr.20251214045245.26">C:\Repos\EKR-Math\temp.pdf
</t>
<t tx="ekr.20251214045245.27">temp.pdf</t>
<t tx="ekr.20251214045245.28"></t>
<t tx="ekr.20251214045245.29">C:\Repos\leo-editor\leo\Icons\LeoLogo.svg

@language rest
@wrap
</t>
<t tx="ekr.20251214045245.3">print ('section one')
</t>
<t tx="ekr.20251214045245.30"></t>
<t tx="ekr.20251214045245.31">Leosplash.GIF
</t>
<t tx="ekr.20251214045245.32"></t>
<t tx="ekr.20251214045245.33">#
# a bar plot with errorbars
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N)  # the x locations for the groups
width = 0.35       # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, menMeans, width, color='r', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, womenMeans, width, color='y', yerr=womenStd)
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))

def autolabel(rects):
    # attach some text labels
    for rect in rects:
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
                '%d' % int(height),
                ha='center', va='bottom')

autolabel(rects1)
autolabel(rects2)
</t>
<t tx="ekr.20251214045245.34"># http://matplotlib.org/1.5.1/examples/animation/basic_example.html

def update_line(num, data, line):
        line.set_data(data[..., :num])
        return line, # a tuple.

fig1 = plt.figure()
data = np.random.rand(2, 100) ### 2, 25)
# g.printObj(data)
l, = plt.plot([], [], 'r-')
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, 25,
    fargs=(data, l), interval=50, blit=True)
# Retain a reference to animation.
g.app.permanentScriptDict['pyplot_animations'] = animation

</t>
<t tx="ekr.20251214045245.35"># http://matplotlib.org/1.5.1/examples/animation/basic_example.html

fig2 = plt.figure()
x = np.arange(-9, 10)
y = np.arange(-9, 10).reshape(-1, 1)
base = np.hypot(x, y)
images = []
for add in np.arange(15):
    images.append((plt.pcolor(x, y, base + add, norm=plt.Normalize(0, 30)),))
animation = animation.ArtistAnimation(fig2, images,
    interval=50,
    repeat_delay=30,
    blit=True)
# Retain a reference to animation.
g.app.permanentScriptDict['pyplot_animations'] = animation
</t>
<t tx="ekr.20251214045245.36"># http://matplotlib.org/1.5.1/examples/animation/animate_decay.html
# import numpy as np
# import matplotlib.pyplot as plt
# import matplotlib.animation as animation

@others

# global data.
fig, ax = plt.subplots()
line, = ax.plot([], [], lw=2)
ax.grid()
xdata, ydata = [], []
animation = animation.FuncAnimation(
    fig, run, data_gen, blit=False, interval=10,
    repeat=False, init_func=init,
    cache_frame_data=False,
)
# Retain a reference to animation.
g.app.permanentScriptDict['pyplot_animations'] = animation
</t>
<t tx="ekr.20251214045245.37">def data_gen(t=0):
    for n in range(500): # n not used.
        t += 0.1
        yield t, np.sin(2*np.pi*t) * np.exp(-t/10.0)
</t>
<t tx="ekr.20251214045245.38">def init():
    ax.set_ylim(-1.1, 1.1)
    ax.set_xlim(0, 10)
    del xdata[:]
    del ydata[:]
    line.set_data(xdata, ydata)
    return line,
</t>
<t tx="ekr.20251214045245.39">def run(data):
    # update the data
    t, y = data
    xdata.append(t)
    ydata.append(y)
    xmin, xmax = ax.get_xlim()
    if t &gt;= xmax:
        ax.set_xlim(xmin, 2*xmax)
        ax.figure.canvas.draw()
    line.set_data(xdata, ydata)
    return line,
</t>
<t tx="ekr.20251214045245.4">1 == 2
</t>
<t tx="ekr.20251214045245.40">x1 = np.linspace(0.0, 5.0)
x2 = np.linspace(0.0, 2.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
y2 = np.cos(2 * np.pi * x2)
# Grpah 1.
plt.subplot(2, 1, 1)
plt.plot(x1, y1, 'ko-')
plt.title('A tale of 2 subplots')
plt.ylabel('Damped oscillation')
# Graph 2.
plt.subplot(2, 1, 2)
plt.plot(x2, y2, 'r.-')
plt.xlabel('time (s)')
plt.ylabel('Undamped')</t>
<t tx="ekr.20251214045245.41"></t>
<t tx="ekr.20251214045245.42">@nocolor-node
&lt;?xml version="1.0" standalone="no"?&gt;
&lt;!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"&gt;
&lt;svg width="20cm" height="15cm" viewBox="0 0 800 600"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlik/"
     baseProfile="tiny" version="1.2"&gt;
   &lt;title&gt;Spheres&lt;/title&gt;
   &lt;desc&gt;Semi-transparent bubbles on a colored background.&lt;/desc&gt;
     &lt;defs&gt;
       &lt;!-- Create radial gradients for each bubble. --&gt;
       &lt;radialGradient id="blueBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#cdcdff" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#cdaacd" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="redBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#ffcdcd" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#bbbb99" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="greenBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#cdffcd" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#99aaaa" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="yellowBubble" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" stop-opacity="1" /&gt;
         &lt;stop offset="25%" stop-color="#ffffcd" stop-opacity=".65" /&gt;
         &lt;stop offset="100%" stop-color="#bbbbaa" stop-opacity=".75" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="background" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="400" fx="250" fy="250"&gt;
         &lt;stop offset="0%" stop-color="#ffffee" /&gt;
         &lt;stop offset="100%" stop-color="#ccccaa" /&gt;
       &lt;/radialGradient&gt;
       &lt;linearGradient id="surface" gradientUnits="userSpaceOnUse"
                       x1="-100" y1="200" x2="400" y2="200"&gt;
         &lt;stop offset="0%" stop-color="#ffffcc" /&gt;
         &lt;stop offset="100%" stop-color="#bbbb88" /&gt;
       &lt;/linearGradient&gt;

       &lt;!-- Create radial gradients for each circle to make them look like
            spheres. --&gt;
       &lt;radialGradient id="blueSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="blue" /&gt;
         &lt;stop offset="100%" stop-color="#222244" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="redSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="red" /&gt;
         &lt;stop offset="100%" stop-color="#442222" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="greenSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="green" /&gt;
         &lt;stop offset="100%" stop-color="#113311" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="yellowSphere" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="-50"&gt;
         &lt;stop offset="0%" stop-color="white" /&gt;
         &lt;stop offset="75%" stop-color="yellow" /&gt;
         &lt;stop offset="100%" stop-color="#444422" /&gt;
       &lt;/radialGradient&gt;
       &lt;radialGradient id="shadowGrad" gradientUnits="userSpaceOnUse"
                       cx="0" cy="0" r="100" fx="-50" fy="50"&gt;
         &lt;stop offset="0%" stop-color="black" stop-opacity="1.0" /&gt;
         &lt;stop offset="100%" stop-color="black" stop-opacity="0.0" /&gt;
       &lt;/radialGradient&gt;

       &lt;!-- Define a shadow for each sphere. --&gt;
       &lt;circle id="shadow" fill="url(#shadowGrad)" cx="0" cy="0" r="100" /&gt;
       &lt;g id="bubble"&gt;
         &lt;circle fill="black" cx="0" cy="0" r="50" /&gt;
         &lt;circle fill="#a6ce39" cx="0" cy="0" r="33" /&gt;
         &lt;path fill="black" d="M 37,50 L 50,37 L 12,-1 L 22,-11 L 10,-24 L -24,10
                               L -11,22 L -1,12 Z" /&gt;
         &lt;circle cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/defs&gt;
   &lt;g&gt;
     &lt;rect fill="url(#background)" x="0" y="0" width="800" height="600" /&gt;
   &lt;/g&gt;

   &lt;g transform="translate(200,700)"&gt;
     &lt;use xlink:href="#bubble" fill="url(#blueBubble)" /&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="1s" dur="10s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(315,700)"&gt;
     &lt;g transform="scale(0.5,0.5)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#redBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="3s" dur="7s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(80,700)"&gt;
     &lt;g transform="scale(0.65,0.65)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#greenBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="5s" dur="9s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(255,700)"&gt;
     &lt;g transform="scale(0.3,0.3)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#yellowBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="2s" dur="6s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(565,700)"&gt;
     &lt;g transform="scale(0.4,0.4)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#blueBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="4s" dur="8s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(715,700)"&gt;
     &lt;g transform="scale(0.6,0.6)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#redBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="1s" dur="4s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(645,700)"&gt;
     &lt;g transform="scale(0.375,0.375)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#greenBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="0s" dur="11s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(555,700)"&gt;
     &lt;g transform="scale(0.9,0.9)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#yellowBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="3s" dur="7.5s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;

   &lt;g transform="translate(360,700)"&gt;
     &lt;g transform="scale(0.5,0.5)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#blueBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="3s" dur="6s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(215,700)"&gt;
     &lt;g transform="scale(0.45,0.45)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#redBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="5.5s" dur="7s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(420,700)"&gt;
     &lt;g transform="scale(0.75,0.75)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#greenBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="1s" dur="9s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;
   &lt;g transform="translate(815,700)"&gt;
     &lt;g transform="scale(0.6,0.6)"&gt;
       &lt;use xlink:href="#bubble" fill="url(#yellowBubble)" /&gt;
     &lt;/g&gt;
     &lt;animateTransform attributeName="transform" type="translate" additive="sum"
         values="0,0; 0,-800" begin="2s" dur="9.5s" fill="freeze" repeatCount="indefinite" /&gt;
   &lt;/g&gt;

   &lt;g transform="translate(225,375)" &gt;
     &lt;g transform="scale(1.0,0.5)" &gt;
       &lt;path d="M 0 0 L 350 0 L 450 450 L -100 450 z"
             fill="url(#surface)" stroke="none" /&gt;
     &lt;/g&gt;
   &lt;/g&gt;
     &lt;g transform="translate(200,0)" &gt;
     &lt;g transform="translate(200,490) scale(2.0,1.0) rotate(45)" &gt;
       &lt;rect fill="#a6ce39" x="-69" y="-69" width="138" height="138" /&gt;
       &lt;circle fill="black" cx="0" cy="0" r="50" /&gt;
       &lt;circle fill="#a6ce39" cx="0" cy="0" r="33" /&gt;
       &lt;path fill="black" d="M 37,50 L 50,37 L 12,-1 L 22,-11 L 10,-24 L -24,10
                             L -11,22 L -1,12 Z" /&gt;
       &lt;animateTransform attributeName="transform"  type="rotate" additive="sum" values="0; 360"
                  begin="0s" dur="10s" fill="freeze" repeatCount="indefinite" /&gt;
     &lt;/g&gt;
     &lt;g transform="translate(200,375)"&gt;
       &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
       &lt;circle fill="url(#blueSphere)" cx="0" cy="0" r="100" /&gt;
     &lt;/g&gt;
     &lt;g transform="translate(315,440)"&gt;
       &lt;g transform="scale(0.5,0.5)"&gt;
         &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
         &lt;circle fill="url(#redSphere)" cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/g&gt;
     &lt;g transform="translate(80,475)"&gt;
       &lt;g transform="scale(0.65,0.65)"&gt;
         &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
         &lt;circle fill="url(#greenSphere)" cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/g&gt;
     &lt;g transform="translate(255,525)"&gt;
       &lt;g transform="scale(0.3,0.3)"&gt;
         &lt;use xlink:href="#shadow" transform="translate(25,55) scale(1.0,0.5)" /&gt;
         &lt;circle fill="url(#yellowSphere)" cx="0" cy="0" r="100" /&gt;
       &lt;/g&gt;
     &lt;/g&gt;
   &lt;/g&gt;
&lt;/svg&gt;</t>
<t tx="ekr.20251214045245.43">C:\Repos\leo-editor\leo\Icons\bubbles.svg
</t>
<t tx="ekr.20251214045245.44">@language rest
@wrap

''' Creates a window for *live* rendering of rst, html, etc.  (Qt only).

Commands
========

viewrendered.py creates the following (``Alt-X``) commands:

``viewrendered``
    opens a new window where the current body text is rendered as HTML
    (if it starts with '&lt;'), or otherwise reStructuredText.
``viewrendered-big``
    as above, but zoomed in, useful for presentations
``viewrendered-html``
    displays the html source generated from reStructuredText, useful for
    debugging

``viewrendered`` sets the process current directory (os.chdir()) to the path
to the node being rendered, to allow relative paths to work in
``.. image::`` directives.

reStructuredText errors and warnings may be shown.  For example, both::

    Heading
    -------

    `This` is **really** a line of text.

and::

    &lt;h1&gt;Heading&lt;h1&gt;

    &lt;tt&gt;This&lt;/tt&gt; is &lt;b&gt;really&lt;/b&gt; a line of text.

will look something like:

**Heading**

`This` is **really** a line of text.

Settings
========

@string view-rendered-default-kind = rst
----------------------------------------

The default kind of rendering.  One of (big,rst,html)
    
@bool view-rendered-auto-create = False
---------------------------------------

When True, the plugin will create a rendering pane automatically.

'''
</t>
<t tx="ekr.20251214045245.45"></t>
<t tx="ekr.20251214045245.46">@language md

# Title

This is **bold** and *italics*.

More info at [Leo's home page](&lt;http://leoeditor.com/).

### Section

- Bullet list
- Another item
- Third item.
</t>
<t tx="ekr.20251214045245.47">Inline:
    
\int_{b^a} \, x^2 \, dx: :math:`\int_{b^a} \, x^2 \, dx`

d/dx \int_{0^x}\,f(u)\,du=f(x): :math:`d/dx \int_{0^x}\,f(u)\,du=f(x)`

\int_{0^x}: :math:`\int_{0^x}`

block:

.. math::

    x + 5 = -3

    \int_{b^a} \, x^2 \, dx

    d / dx \, \left(\int_{0^x} f(u)\,du\right)=f(x)

    d / dx (\int_{0^x} f(u)\,du ) = f(x)

    y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})
    
Alignment with \\\\ in \\begin{equation}:

.. math::

    \begin{equation}
    x + 5 = -3 \\
    \int_{b^a} \, x^2 \, dx\\
    d / dx \left(\int_{0^x} f(u)\,du\right)=f(x)\\
    d / dx (\int_{0^x} f(u)\,du) = f(x)\\
    y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})
    \end{equation}
    
Inline: :math:`y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})`

Inline: :math:`(\sum_{t=0}^{N}\,f(t,k))/N`
    
The final output will be a vector :math:`Y` with :math:`K` elements, :math:`y_k`, where
:math:`y_k(\mathbf{x},\mathbf{w}) = p(C_1|\mathbf{x})` is the probability that node :math:`k` is in class :math:`C_1` and
:math:`p(C_2|\mathbf{x}) = 1-p(C_1|\mathbf{x})`

</t>
<t tx="ekr.20251214045245.48">@language md

- Item

[link](xxx)</t>
<t tx="ekr.20251214045245.49"></t>
<t tx="ekr.20251214045245.5">print('Node1: line 1')
# Blank line with trailing ws
    
print('Node1: line 4')
</t>
<t tx="ekr.20251214045245.50">@language rest

#####
Title
#####

This is **bold** and *italics*.

More info at `Leo's home page &lt;http://leoeditor.com/&gt;`_.

Section
=======

- Bullet list
- Another item
- Third item.</t>
<t tx="ekr.20251214045245.51">@language rest
@wrap

- test2

*italic*

**bold**

@language python</t>
<t tx="ekr.20251214045245.52">@language python

def foo():
    pass</t>
<t tx="ekr.20251214045245.53">@language rest
@wrap
code::

    print('Hello world')
    for i in 0,1:
        print(i)
    
</t>
<t tx="ekr.20251214045245.54"></t>
<t tx="ekr.20251214045245.55">@language md
@wrap

[Leo's home page](https://leo-editor.github.io/leo-editor/)
</t>
<t tx="ekr.20251214045245.56">@language rest
@wrap

`Leo's home page &lt;https://leo-editor.github.io/leo-editor/&gt;`_

</t>
<t tx="ekr.20251214045245.57">@language rest
@wrap

`EKR mail &lt;http://mail.google.com/mail/#inbox&gt;`_</t>
<t tx="ekr.20251214045245.58">@language md
@wrap

[weather.com](http://www.weather.com/weather/today/Madison+WI+53705?from=hp_promolocator&amp;lswe=53705&amp;lwsa=Weather36HourHealthCommand)
</t>
<t tx="ekr.20251214045245.59"></t>
<t tx="ekr.20251214045245.6">@nobeautify

print (
    'abc'
)
</t>
<t tx="ekr.20251214045245.60"></t>
<t tx="ekr.20251214045245.61">@language python
# %%
# Leo created this prefix from @data jupyter-prefix.
# Jupyter's 'New Python Text Notebook with Percent Format` created this file.

# ---
# jupyter:
#   jupytext:
#     formats: py:percent
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %%

</t>
<t tx="ekr.20251214045245.62"></t>
<t tx="ekr.20251214045245.63">@language python
# %%

# ---
# jupyter:
#   jupytext:
#     formats: ipynb,py:percent
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %% [markdown] editable=true slideshow={"slide_type": ""}
# # Creating a new notebook

# %% [markdown]
# 1. Open the command palette with the shortcut: `Ctrl/Command` + `Shift` + `P`
# 2. Search for the command `Create New Blank Notebook`

# %% editable=true slideshow={"slide_type": ""}
# How to get back to the start page

# %% editable=true slideshow={"slide_type": ""}
1. Open the command palette with the shortcut: `Ctrl/Command` + `Shift` + `P`

2. Search for the command `Python: Open Start Page`

# %% [markdown] editable=true slideshow={"slide_type": ""}
# # Getting started

# %% [markdown]
# You are currently viewing what we call our Notebook Editor. It is an interactive document based on Jupyter Notebooks that supports the intermixing of code, outputs and markdown documentation. 
#
# This cell is a markdown cell. To edit the text in this cell, simply double click on the cell to change it into edit mode.
#
# The next cell below is a code cell. You can switch a cell between code and markdown by clicking on the code ![code icon](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/codeIcon.PNG) /markdown ![markdown icon](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/markdownIcon.PNG) icons or using the keyboard shortcut `M` and `Y` respectively.

# %%
print("hello world")

# %% [markdown]
# * To execute the code in the cell above, click on the cell to select it and then either press the play ![play](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/playIcon.PNG) button in the cell toolbar, or use the keyboard shortcut `Ctrl/Command` + `Enter`.
# * To edit the code, just click in cell and start editing.
# * To add a new cell below, click the `Add Cell` icon ![add cell](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/addIcon.PNG) at the bottom left of the cell or enter command mode with the `ESC` Key and then use the keyboard shortcut `B` to create the new cell below.
#

# %% [markdown]
# # Features

# %% [markdown]
# **Variable explorer**
#
# To view all your active variables and their current values in the notebook, click on the variable explorer icon ![variable explorer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/variableExplorerIcon.PNG) in the top toolbar.
#
# ![Variable Explorer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/variableexplorer.png)
#
# **Data Viewer**
#
# To view your data frame in a more visual "Excel" like format, open the variable explorer and to the left of any dataframe object, you will see the data viewer icon ![data viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/dataViewerIcon.PNG) which you can click to open the data viewer.
#
# ![Data Viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/dataviewer.gif)
#
# **Convert to Python File**
#
# To export your notebook to a Python file (.py), click on the `Convert to Python script` icon ![Export icon](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/exportIcon.PNG) in the top toolbar 
#
# ![Export](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/savetopythonfile.png)
#
# **Plot Viewer**
#
# If you have a graph (such as matplotlib) in your output, you'll notice if you hover over the graph, the `Plot Viewer` icon ![Plot Viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/plotViewerIcon.PNG) will appear in the top left. Click the icon to open up the graph in the Plotviewer which allows you to zoom on your plots and export it in formats such as png and jpeg.
#
# ![Plot Viewer](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/plotviewer.gif)
#
# **Switching Kernels**
#
# The notebook editor will detect all kernels in your system by default. To change your notebook kernel, click on the kernel status in the top toolbar at the far right. For example, your kernel status may say "Python 3: Idle". This will open up the kernel selector where you can choose your desired kernel.
#
# ![Switching Kernels](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/kernelchange.gif)
#
# **Remote Jupyter Server**
#
# To connect to a remote Jupyter server, open the command prompt and search for the command `Specify remote or local Jupyter server for connections`. Then select `Existing` and enter the remote Jupyter server URL. Afterwards, you'll be prompted to reload the window and the Notebook will be opened connected to the remote Jupyter server.
#
# ![Remote](https://raw.githubusercontent.com/microsoft/vscode-python/main/images/remoteserver.gif)

# %% [markdown] colab_type="text" id="-Rh3-Vt9Nev9"
# # More Resources

# %% [markdown]
# - [Data science tutorial for Visual Studio Code](https://code.visualstudio.com/docs/python/data-science-tutorial)
# - [Jupyter Notebooks in Visual Studio Code documentation](https://code.visualstudio.com/docs/python/jupyter-support)
</t>
<t tx="ekr.20251214045245.64">@language python
# %%
# A leading (misleading?) comment.
&lt;&lt; preamble &gt;&gt;
@others
@language json
</t>
<t tx="ekr.20251214045245.65"># %%
# ---
# jupyter:
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

</t>
<t tx="ekr.20251214045245.66">@language python
</t>
<t tx="ekr.20251214045245.67"># %%
2 + 666 + 4
</t>
<t tx="ekr.20251214045245.68"># %%
print('hi changed externally')
</t>
<t tx="ekr.20251214045245.69"># %% [markdown]
# This is a markdown cell

# %% [markdown]
# Another markdown cell

# %%
</t>
<t tx="ekr.20251214045245.7"></t>
<t tx="ekr.20251214045245.70">&lt;&lt; prefix &gt;&gt;
@others
@language python
@tabwidth -4
</t>
<t tx="ekr.20251214045245.71"># ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

</t>
<t tx="ekr.20251214045245.72"># %% [markdown]
# Loading and Analyzing Data
# ==========================

</t>
<t tx="ekr.20251214045245.73"># %%
import pandas as pd  # programmers like shortening names for things, so they usually import pandas as "pd"
import altair as alt # again with the shortening of names
import requests # we use it for downloading the data so we don't have to save it on GitHub (too big!)
import zipfile # for de-compressing the files we download from the EPA

</t>
<t tx="ekr.20251214045245.74"># %% [markdown]
# ## Load the air quality data
# The EPA published PM2.5 daily summary files annually [on their website](https://aqs.epa.gov/aqsweb/airdata/download_files.html#Daily). This data is the "PM2.5 FRM/FEM Mass (88101)" dataset.
# Pandas understands what a CSV file is, so here we can just load them into two `DataFrame`s. A data frame is simply one representation of a table of data. It is the most important form of storage for working with data in pandas.

</t>
<t tx="ekr.20251214045245.75"># %%
# Download the data from the EPA website
data_file_urls = [
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2020.zip',
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2019.zip'
]
# copied this example from https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests
for url in data_file_urls:
    local_filename = "data/{}".format(url.split('/')[-1])
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192): 
                f.write(chunk)
# and unzip the files
files_to_unzip = ["data/{}".format(url.split('/')[-1]) for url in data_file_urls]
for f in files_to_unzip:
    with zipfile.ZipFile(f,"r") as zip_ref:
        zip_ref.extractall("data")

</t>
<t tx="ekr.20251214045245.76"># %%
air_2019_df = pd.read_csv("data/daily_88101_2019.csv")
air_2020_df = pd.read_csv("data/daily_88101_2020.csv")
air_2020_df.head() # this helpfully prints out the first few rows with headers to preview the data

</t>
<t tx="ekr.20251214045245.77"># %%
"{} rows for 2019, {} rows for 2020".format(air_2019_df.shape[0], air_2019_df.shape[0])

</t>
<t tx="ekr.20251214045245.78"># %% [markdown]
# ## Aggregate and average MA data by city
# Let's compare MA data by city in 2020 and 2019.

</t>
<t tx="ekr.20251214045245.79"># %%
# Step 1 - filter the data down by state name

is_MA_data = air_2020_df['State Name'] == "Massachusetts"
air_MA_2020_df = air_2020_df[is_MA_data]

is_MA_data = air_2019_df['State Name'] == "Massachusetts"
air_MA_2019_df = air_2019_df[is_MA_data]

"{} reports for MA in 2019, {} reports for MA in 2020".format(air_MA_2019_df.shape[0], air_MA_2020_df.shape[0])

</t>
<t tx="ekr.20251214045245.8">@language rust

@
Docstring: *italics* and **bold**.
@c

#[cfg(test)]
mod tests;
</t>
<t tx="ekr.20251214045245.80"># %%
# now trim down to just the columns we care about so it is easier to understand
interesting_columns = ['City Name', 'Latitude', 'Longitude', 'Arithmetic Mean']
air_MA_2020_df = pd.DataFrame(air_MA_2020_df, columns=interesting_columns)
air_MA_2019_df = pd.DataFrame(air_MA_2019_df, columns=interesting_columns)
air_MA_2019_df

</t>
<t tx="ekr.20251214045245.81"># %%
# now group all the records by city and average them
avg_by_city_2020_MA_df = air_MA_2020_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
avg_by_city_2019_MA_df = air_MA_2019_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
# now we need to add in a year column so we can tell the data apart!
avg_by_city_2020_MA_df['year'] = 2020
avg_by_city_2019_MA_df['year'] = 2019
# now we can just contacetane the two dataframes to get all our data in one place
ma_city_avg_df = avg_by_city_2019_MA_df.append(avg_by_city_2020_MA_df)
ma_city_avg_df.to_csv('data/MA-city-year-avg.csv')
ma_city_avg_df

</t>
<t tx="ekr.20251214045245.82"># %% [markdown]
# ## Visually Inspect the Data

</t>
<t tx="ekr.20251214045245.83"># %%
alt.Chart(ma_city_avg_df, height=300).mark_bar().encode(
    alt.X('year:N'),
    alt.Y('Mean'),
    color='year:N',
    column=alt.Column(field='City', type='ordinal', spacing=10)
).properties( 
    title="MA City Average PM2.5 (by year)",
)
</t>
<t tx="ekr.20251214045245.84">@language jupytext
&lt;&lt; prefix &gt;&gt;
@others
@tabwidth -4
</t>
<t tx="ekr.20251214045245.85">@language jupytext
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.16.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# %%
</t>
<t tx="ekr.20251214045245.86">@language jupytext

# %% [markdown]
# Loading and Analyzing Data
# ==========================

</t>
<t tx="ekr.20251214045245.87"># %%
import pandas as pd  # programmers like shortening names for things, so they usually import pandas as "pd"
import altair as alt # again with the shortening of names
import requests # we use it for downloading the data so we don't have to save it on GitHub (too big!)
import zipfile # for de-compressing the files we download from the EPA

</t>
<t tx="ekr.20251214045245.88"># %% [markdown]
# ## Load the air quality data
# The EPA published PM2.5 daily summary files annually [on their website](https://aqs.epa.gov/aqsweb/airdata/download_files.html#Daily). This data is the "PM2.5 FRM/FEM Mass (88101)" dataset.
# Pandas understands what a CSV file is, so here we can just load them into two `DataFrame`s. A data frame is simply one representation of a table of data. It is the most important form of storage for working with data in pandas.

</t>
<t tx="ekr.20251214045245.89"># %%
# Download the data from the EPA website
data_file_urls = [
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2020.zip',
    'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2019.zip'
]
# copied this example from https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests
for url in data_file_urls:
    local_filename = "data/{}".format(url.split('/')[-1])
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192): 
                f.write(chunk)
# and unzip the files
files_to_unzip = ["data/{}".format(url.split('/')[-1]) for url in data_file_urls]
for f in files_to_unzip:
    with zipfile.ZipFile(f,"r") as zip_ref:
        zip_ref.extractall("data")

</t>
<t tx="ekr.20251214045245.9">@language rust

/// Rust comment.

#[cfg(test)]
mod tests;
</t>
<t tx="ekr.20251214045245.90"># %%
air_2019_df = pd.read_csv("data/daily_88101_2019.csv")
air_2020_df = pd.read_csv("data/daily_88101_2020.csv")
air_2020_df.head() # this helpfully prints out the first few rows with headers to preview the data

</t>
<t tx="ekr.20251214045245.91"># %%
"{} rows for 2019, {} rows for 2020".format(air_2019_df.shape[0], air_2019_df.shape[0])

</t>
<t tx="ekr.20251214045245.92"># %% [markdown]
# ## Aggregate and average MA data by city
# Let's compare MA data by city in 2020 and 2019.

</t>
<t tx="ekr.20251214045245.93"># %%
# Step 1 - filter the data down by state name

is_MA_data = air_2020_df['State Name'] == "Massachusetts"
air_MA_2020_df = air_2020_df[is_MA_data]

is_MA_data = air_2019_df['State Name'] == "Massachusetts"
air_MA_2019_df = air_2019_df[is_MA_data]

"{} reports for MA in 2019, {} reports for MA in 2020".format(air_MA_2019_df.shape[0], air_MA_2020_df.shape[0])

</t>
<t tx="ekr.20251214045245.94"># %%
# now trim down to just the columns we care about so it is easier to understand
interesting_columns = ['City Name', 'Latitude', 'Longitude', 'Arithmetic Mean']
air_MA_2020_df = pd.DataFrame(air_MA_2020_df, columns=interesting_columns)
air_MA_2019_df = pd.DataFrame(air_MA_2019_df, columns=interesting_columns)
air_MA_2019_df

</t>
<t tx="ekr.20251214045245.95"># %%
# now group all the records by city and average them
avg_by_city_2020_MA_df = air_MA_2020_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
avg_by_city_2019_MA_df = air_MA_2019_df.groupby('City Name').mean()\
    .reset_index()\
    .rename(columns={'City Name': 'City', 'Arithmetic Mean': 'Mean'})
# now we need to add in a year column so we can tell the data apart!
avg_by_city_2020_MA_df['year'] = 2020
avg_by_city_2019_MA_df['year'] = 2019
# now we can just contacetane the two dataframes to get all our data in one place
ma_city_avg_df = avg_by_city_2019_MA_df.append(avg_by_city_2020_MA_df)
ma_city_avg_df.to_csv('data/MA-city-year-avg.csv')
ma_city_avg_df

</t>
<t tx="ekr.20251214045245.96"># %% [markdown]
# ## Visually Inspect the Data

</t>
<t tx="ekr.20251214045245.97"># %%
alt.Chart(ma_city_avg_df, height=300).mark_bar().encode(
    alt.X('year:N'),
    alt.Y('Mean'),
    color='year:N',
    column=alt.Column(field='City', type='ordinal', spacing=10)
).properties( 
    title="MA City Average PM2.5 (by year)",
)
</t>
<t tx="ekr.20251214045245.98"># jm = g.app.jupytextManager
import io
import jupytext
path = r'c:/test/example-data-analysis.ipynb'
last = c.lastTopLevel()
p = last.insertAfter()
p.h = f"Test: {path}"
# The guts of jm.read.
fmt = 'py:percent'
notebook = jupytext.read(path, fmt=fmt)
with io.StringIO() as f:
    jupytext.write(notebook, f, fmt=fmt)
    contents = f.getvalue()
    p.b = contents
c.redraw(p)
</t>
<t tx="ekr.20251214045245.99">@language matlab

clear all, close all, clc

A=imread('../DATA/dog.jpg');
X=double(rgb2gray(A)); % Convert RBG-&gt;gray, 256 bit-&gt;double.
nx = size(X,1); ny = size(X,2);
imagesc(X), axis off, colormap gray 

[U,S,V] = svd(X);

for r=[5 20 100];  % Truncation value
    Xapprox = U(:,1:r)*S(1:r,1:r)*V(:,1:r)'; % Approx. image
    figure, imagesc(Xapprox), axis off
    title(['r=',num2str(r,'%d'),']);
end

%% f_ch01_ex02_2
subplot(1,2,1), semilogy(diag(S),'k') 
subplot(1,2,2), plot(cumsum(diag(S))/sum(diag(S)),'k') 
</t>
</tnodes>
</leo_file>
