├── .gitignore ├── .travis.yml ├── ChangeLog ├── MANIFEST.in ├── README.rst ├── requirements.txt ├── rst2confluence.py ├── rst2confluence ├── __init__.py └── confluence.py ├── rst2tree.py ├── run-tests.sh ├── setup.py ├── test ├── .gitignore ├── admonitions.rst ├── admonitions.rst.exp ├── block_quote.rst ├── block_quote.rst.exp ├── bug1-bold-sublist.rst ├── bug1-bold-sublist.rst.exp ├── bug2-flowingtext-literal.rst ├── bug2-flowingtext-literal.rst.exp ├── bug3-note-indent.rst ├── bug3-note-indent.rst.exp ├── bug4-only-bold.rst ├── bug4-only-bold.rst.exp ├── bullet_list.rst ├── bullet_list.rst.exp ├── bullet_list_paragraphs.rst ├── bullet_list_paragraphs.rst.exp ├── bullet_list_table.rst ├── bullet_list_table.rst.exp ├── cat.jpg ├── definition_list.rst ├── definition_list.rst.exp ├── docinfo.rst ├── docinfo.rst.exp ├── enumerated_list.rst ├── enumerated_list.rst.exp ├── escaping.rst ├── escaping.rst.exp ├── excerpt-disabled.rst ├── excerpt-disabled.rst.exp ├── excerpt-enabled-complex.rst ├── excerpt-enabled-complex.rst.exp ├── excerpt-enabled.rst ├── excerpt-enabled.rst.exp ├── field_lists.rst ├── field_lists.rst.exp ├── figure.rst ├── figure.rst.exp ├── footnote-auto.rst ├── footnote-auto.rst.exp ├── footnote-manual.rst ├── footnote-manual.rst.exp ├── footnote-mixed.rst ├── footnote-mixed.rst.exp ├── gridtable.rst ├── gridtable.rst.exp ├── image-gallery.rst ├── image-gallery.rst.exp ├── image.rst ├── image.rst.exp ├── line-blocks-in-a-field-list.rst ├── line-blocks-in-a-field-list.rst.exp ├── line-blocks.rst ├── line-blocks.rst.exp ├── linebreak-bold.rst ├── linebreak-bold.rst.exp ├── linebreak.rst ├── linebreak.rst.exp ├── links.rst ├── links.rst.exp ├── literal.rst ├── literal.rst.exp ├── paragraph.rst ├── paragraph.rst.exp ├── reference.rst ├── reference.rst.exp ├── roles-title-reference.rst ├── roles-title-reference.rst.exp ├── section-h1.rst ├── section-h1.rst.exp ├── section.rst ├── section.rst.exp ├── section_reference.rst ├── section_reference.rst.exp ├── simpletable.rst ├── simpletable.rst.exp ├── substitution.rst ├── substitution.rst.exp ├── text_effects.rst ├── text_effects.rst.exp ├── toc.rst └── toc.rst.exp └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | MANIFEST 3 | dist 4 | README.html 5 | build 6 | scripts/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: bionic 3 | sudo: false 4 | python: 5 | - 2.7 6 | - 3.6 7 | - 3.7 8 | - 3.8 9 | - 3.9 10 | matrix: 11 | allow_failures: 12 | - python: 2.6 13 | dist: trusty 14 | - python: 3.2 15 | dist: trusty 16 | - python: 3.3 17 | dist: trusty 18 | - python: 3.4 19 | - python: 3.5 20 | - python: 3.10 21 | addons: 22 | apt: 23 | packages: 24 | - colordiff 25 | script: 26 | - ./run-tests.sh 27 | - flake8 . 28 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2015-09-15 Christian Weiske 2 | 3 | * Remove meta directive output 4 | * Add support for automatic excerpt markup generation 5 | * Add travis-ci.org configuration; see 6 | https://travis-ci.org/netresearch/rst2confluence 7 | * Version 0.6.0 8 | 9 | 2015-09-05 Florian Rathgeber 10 | 11 | * Make the code base PEP8/flake8 compliant 12 | * Add a tox.ini file with flake8 configuration 13 | * Add handling of classifiers in defintion lists 14 | 15 | 2014-08-13 Adrian Castravete 16 | 17 | * Fix weird splitline behaviour 18 | * Version 0.5.1 19 | 20 | 2014-08-01 Christian Weiske 21 | 22 | * Version 0.5.0 23 | 24 | 2014-02-24 Glenn Hutchings 25 | 26 | * Add support for substitution definitions. 27 | 28 | 2014-03-14 José Manuel Fardello 29 | 30 | * Add meta directive support 31 | 32 | 2014-01-20 Christian Weiske 33 | 34 | * Fix bug #7: Crash on document with a single bold word 35 | 36 | 2013-07-12 Christian Weiske 37 | 38 | * Add image gallery support 39 | 40 | 2013-07-12 Christian Weiske 41 | 42 | * Fix single URLs in table cells 43 | * Fix lists within table cells 44 | * Fix multiple paragraphs within table cells 45 | * Version 0.4.2 46 | 47 | 2013-06-20 Christian Weiske 48 | 49 | * Fix rendering of {} inside monospaced text 50 | * Version 0.4.1 51 | 52 | 2013-06-11 Christian Weiske 53 | 54 | * Fix links with [brackets] in URL parameters 55 | * Fix lists nested within a info/note/tip/warning within a list 56 | * Version 0.4 57 | 58 | 2013-05-24 Christian Weiske 59 | 60 | * Add "title reference" role support 61 | 62 | 2013-02-08 Christian Weiske 63 | 64 | * Add page-internal anchor support 65 | * Version 0.3 66 | 67 | 2013-01-11 Christian Weiske 68 | 69 | * Fix heading levels to begin always with h1 70 | * Fix literal text in paragraph following a newline 71 | * Add support for lists with multiple paragraphs per item 72 | 73 | 2012-12-27 Christian Weiske 74 | 75 | * Add line blocks "| foo" support 76 | 77 | 2012-12-20 Christian Weiske 78 | 79 | * Add field list support 80 | 81 | 2012-10-30 Christian Weiske 82 | 83 | * Add admonitions (tip, info) support 84 | * Fix brackets within code blocks 85 | 86 | 2012-08-20 Christian Weiske 87 | 88 | * Fix links to internal pages with spaces 89 | 90 | 2012-07-27 Christian Weiske 91 | 92 | * Fix footnotes 93 | 94 | 2012-07-26 Christian Weiske 95 | 96 | * Add "figure" element support 97 | * Unit test runner 98 | 99 | 2012-06-15 Scott S. McCoy 100 | 101 | * Add footnote support 102 | 103 | 2011-11-23 Christian Weiske 104 | 105 | * Add code block support 106 | * Add system message support (errors + warnings) 107 | * Add TOC (table of contents) support 108 | * Fix escaping of [ characters in text 109 | * Setup script 110 | * Version 0.2 111 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include rst2confluence.py 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ********************************************* 2 | Convert reStructuredText to Confluence markup 3 | ********************************************* 4 | .. image:: https://travis-ci.org/netresearch/rst2confluence.svg?branch=master 5 | :target: https://travis-ci.org/netresearch/rst2confluence 6 | 7 | ==================== 8 | Supported directives 9 | ==================== 10 | 11 | - bullet list 12 | - enumerated list 13 | - image, figure, confluence image galleries 14 | - definition list (with classifiers) 15 | - simple and grid tables 16 | - block quote 17 | - text effect 18 | 19 | - strong 20 | - emphasis 21 | - monospace 22 | - code blocks 23 | - links 24 | - admonitions 25 | 26 | - info 27 | - note 28 | - tip 29 | - warning 30 | - field lists 31 | - line blocks (except nested ones) 32 | - meta directives 33 | - substitutions 34 | 35 | 36 | Additional features 37 | =================== 38 | 39 | Excerpts 40 | -------- 41 | When using the command line option ``--excerpt``, your 42 | confluence code will mark up the first paragraph as excerpt__. 43 | 44 | __ https://confluence.atlassian.com/doc/excerpt-macro-148062.html 45 | 46 | 47 | Image galleries 48 | --------------- 49 | Images and figures with class names that begin with 50 | ``gallery-`` form a gallery:: 51 | 52 | .. image:: cat.jpg 53 | :class: gallery-1 54 | .. image:: dog.jpg 55 | :class: gallery-1 56 | .. figure:: horse.jpg 57 | :class: gallery-2 58 | :scale: 50% 59 | 60 | This creates two galleries: One with cat and dog, the other one with 61 | the horse picture only. 62 | All attributes are ignored on gallery images. 63 | 64 | Gallery-classed images are converted to ``{gallery:include=a.jpg,b.jpg}`` 65 | Confluence markup. 66 | 67 | ===== 68 | Usage 69 | ===== 70 | :: 71 | 72 | ./rst2confluence.py /path/to/file.rst 73 | 74 | 75 | ============ 76 | Installation 77 | ============ 78 | From git checkout:: 79 | 80 | $ sudo ./setup.py install 81 | 82 | Without a git checkout from the `Python Package Index`__:: 83 | 84 | $ pip install rst2confluence 85 | 86 | __ https://pypi.python.org/pypi/rst2confluence 87 | 88 | 89 | ============ 90 | Dependencies 91 | ============ 92 | - ``python-docutils`` 93 | - ``colordiff`` (for running the tests) 94 | 95 | ===== 96 | Tests 97 | ===== 98 | We have some examples how ``rst2confluence`` should behave. 99 | 100 | Check if it does what it should:: 101 | 102 | ./run-tests.sh 103 | 104 | Run a single test:: 105 | 106 | ./run-test.sh test/figure.rst 107 | 108 | 109 | =========== 110 | Other tools 111 | =========== 112 | Use deploy-rst__ to automatically deploy rST documents into confluence. 113 | 114 | 115 | __ https://github.com/netresearch/deploy-rst 116 | 117 | 118 | ======================= 119 | Releasing a new version 120 | ======================= 121 | 122 | 1. Fill ``ChangeLog`` 123 | 2. Update version in ``setup.py`` 124 | 3. Create release tag 125 | 4. Upload to pip:: 126 | 127 | $ ./setup.py sdist upload 128 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | docutils 2 | flake8 3 | urllib3 4 | six -------------------------------------------------------------------------------- /rst2confluence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Confluence Wiki output generator for the Docutils Publisher. 5 | """ 6 | 7 | try: 8 | import locale 9 | locale.setlocale(locale.LC_ALL, '') 10 | except ImportError: 11 | pass 12 | 13 | from docutils.core import publish_cmdline, default_description 14 | 15 | from rst2confluence import confluence 16 | 17 | 18 | description = ('Generates documents in Confluence Wiki format from standalone ' 19 | 'reStructuredText sources. ' + default_description) 20 | 21 | publish_cmdline(writer=confluence.Writer(), description=description) 22 | -------------------------------------------------------------------------------- /rst2confluence/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netresearch/rst2confluence/c8296828b53adec1094871cb28723bb34d87df02/rst2confluence/__init__.py -------------------------------------------------------------------------------- /rst2confluence/confluence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | try: # Python 3 5 | from urllib.parse import unquote 6 | except ImportError: # Python 2 7 | from urllib import unquote 8 | 9 | import six 10 | 11 | from docutils import frontend, nodes, writers 12 | 13 | # sys.stdout = codecs.getwriter('shift_jis')(sys.stdout) 14 | 15 | 16 | class Writer(writers.Writer): 17 | 18 | # Prevent the filtering of the Meta directive. 19 | supported = ['html'] 20 | 21 | settings_spec = ( 22 | 'rST-specific options', 23 | None, 24 | ( 25 | ( 26 | 'Generate {{excerpt}} around the first paragraph', 27 | ['--excerpt'], 28 | { 29 | 'action': 'store_true', 30 | 'validator': frontend.validate_boolean 31 | } 32 | ), 33 | )) 34 | 35 | def translate(self): 36 | self.visitor = ConfluenceTranslator(self.document) 37 | self.visitor.meta = {} 38 | self.document.walkabout(self.visitor) 39 | # Save some metadata as a comment, one per line. 40 | self.output = six.text_type() 41 | self.output += self.visitor.astext() 42 | 43 | 44 | class ConfluenceTranslator(nodes.NodeVisitor): 45 | """Write output in Confluence Wiki format. 46 | 47 | References: 48 | * ReST: http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html 49 | * Confluence Wiki: 50 | http://confluence.atlassian.com/display/DOC/Confluence+Notation+Guide+Overview 51 | """ 52 | 53 | empty_methods = [ 54 | 'depart_Text', 55 | 'depart_colspec', 56 | 'depart_decoration', 57 | 'depart_document', 58 | 'depart_field', 59 | 'depart_footer', 60 | 'depart_line_block', 61 | 'depart_raw', 62 | 'depart_target', 63 | 'depart_tgroup', 64 | 'visit_colspec', 65 | 'visit_decoration', 66 | 'visit_document', 67 | 'visit_field', 68 | 'visit_line', 69 | 'visit_raw', 70 | 'visit_tgroup' 71 | ] 72 | 73 | inCode = False 74 | keepLineBreaks = False 75 | lastTableEntryBar = 0 76 | docinfo = False 77 | generateExcerpt = False 78 | meta = {} 79 | 80 | def __init__(self, document): 81 | nodes.NodeVisitor.__init__(self, document) 82 | self.settings = document.settings 83 | 84 | if self.settings.excerpt: 85 | self.generateExcerpt = True 86 | 87 | self.content = [] 88 | 89 | self.first = True 90 | self.firstParagraph = True 91 | self.list_level = 0 92 | self.section_level = 0 93 | self.list_counter = -1 94 | 95 | self.list_prefix = [[]] 96 | self.lineBeginsWithListIndicator = False 97 | self.addedNewline = False 98 | 99 | self.table = False 100 | self.table_header = False 101 | 102 | self.element_level = 0 103 | self.quote_level = 0 104 | 105 | self.figure = False 106 | self.figureImage = False 107 | self.lastGalleryClass = "" 108 | 109 | self.inTitle = False 110 | 111 | self.openFootnotes = 0 112 | 113 | # Block all output 114 | self.block = False 115 | self.footnote = False 116 | self.field_body = False 117 | 118 | for method in self.empty_methods: 119 | setattr(self, method, lambda n: None) 120 | 121 | def _add(self, string): 122 | if not self.block: 123 | self.addedNewline = False 124 | self.content.append(string) 125 | 126 | def _indent(self): 127 | self._add(" " * self.list_level * 2) 128 | 129 | def _newline(self, number=1): 130 | if self.addedNewline and self.table: 131 | self.content[self.lastTableEntryBar] += "{div}" 132 | self.tableEntryDiv = True 133 | self._add("\n" * number) 134 | self.addedNewline = True 135 | self.lineBeginsWithListIndicator = False 136 | 137 | def _remove_last_newline(self): 138 | if self.addedNewline: 139 | self.content.pop(len(self.content) - 1) 140 | self.addedNewline = False 141 | 142 | def astext(self): 143 | return "".join(self.content) 144 | 145 | def unknown_visit(self, node): 146 | raise Exception( 147 | "Unknown visit on line %s: %s." % (node.line, repr(node))) 148 | 149 | def unknown_departure(self, node): 150 | raise Exception( 151 | "Unknown departure on line %s: %s." % (node.line, repr(node))) 152 | 153 | def visit_paragraph(self, node): 154 | if self.firstParagraph and self.generateExcerpt\ 155 | and self.element_level == 0: 156 | self._add('{excerpt}') 157 | 158 | self.element_level += 1 159 | if not self.first and not self.footnote and not self.field_body\ 160 | and self.list_level == 0: 161 | self._newline() 162 | if self.list_level > 0 and not self.lineBeginsWithListIndicator: 163 | self._add(" " * (self.list_level + (self.list_level > 0))) 164 | 165 | def depart_paragraph(self, node): 166 | if self.firstParagraph and self.element_level == 1: 167 | if self.generateExcerpt: 168 | self._add('{excerpt}') 169 | self.firstParagraph = False 170 | if not self.footnote and not isinstance(node.parent, nodes.field_body): 171 | self._newline() 172 | self.element_level -= 1 173 | self.first = False 174 | 175 | def visit_Text(self, node): 176 | string = node.astext() 177 | if not self.inCode: 178 | string = string.replace("[", "\\[")\ 179 | .replace('{', '{')\ 180 | .replace('}', '}') 181 | if self.keepLineBreaks: 182 | self._add(string) 183 | else: 184 | # rst line break should be removed. 185 | self._add(" ".join(string.split('\n'))) 186 | 187 | def visit_emphasis(self, node): 188 | self.element_level += 1 189 | self._add("_") 190 | 191 | def depart_emphasis(self, node): 192 | self._add("_") 193 | self.element_level -= 1 194 | 195 | def visit_strong(self, node): 196 | self.element_level += 1 197 | self._add_space_when_needed() 198 | self._add("*") 199 | 200 | def depart_strong(self, node): 201 | self._add("*") 202 | self.element_level -= 1 203 | 204 | def visit_section(self, node): 205 | self.section_level += 1 206 | 207 | def depart_section(self, node): 208 | self.section_level -= 1 209 | 210 | def cflAnchorValue(self, name): 211 | return name.replace("-", "")\ 212 | .replace(" ", '')\ 213 | .replace(u"ä", "a")\ 214 | .replace(u"ö", "o")\ 215 | .replace(u"ü", "u")\ 216 | .replace(u"ß", "s") 217 | 218 | def visit_target(self, node): 219 | if 'refid' not in node: 220 | return 221 | self._add("{anchor:" + self.cflAnchorValue(node["refid"]) + "}") 222 | self._newline() 223 | 224 | def visit_reference(self, node): 225 | if 'refuri' in node: 226 | if node.children[0].astext() == node["refuri"]\ 227 | and "://" in node["refuri"]: 228 | if self.table and self._endswidth("|"): 229 | self._add(" ") 230 | self._add(self.escapeUri(node.children[0].astext())) 231 | elif "://" in node["refuri"]: 232 | self._add("[") 233 | self._add(node.children[0].astext() + "|") 234 | self._add(self.escapeUri(node["refuri"]) + "]") 235 | else: 236 | self._add("[") 237 | self._add(node.children[0].astext() + "|") 238 | self._add(unquote(node["refuri"]) + "]") 239 | 240 | else: 241 | assert 'refid' in node, \ 242 | 'References must have "refuri" or "refid" attribute.' 243 | self._add("[") 244 | self._add(node.children[0].astext() + "|") 245 | self._add("#" + self.cflAnchorValue(node["refid"]) + "]") 246 | 247 | raise nodes.SkipNode 248 | 249 | def escapeUri(self, uri): 250 | return uri.replace("[", "\\[").replace("]", "\\]") 251 | 252 | def depart_reference(self, node): 253 | pass 254 | 255 | def visit_footnote_reference(self, node): 256 | self.openFootnotes += 1 257 | self._add("^") 258 | self._add(node.children[0].astext()) 259 | self._add("^") 260 | 261 | raise nodes.SkipNode 262 | 263 | def depart_footnote_reference(self, node): 264 | pass 265 | 266 | def visit_footnote(self, node): 267 | self.openFootnotes -= 1 268 | self.footnote = True 269 | self._newline() 270 | self._add("bq. ") 271 | 272 | def depart_footnote(self, node): 273 | self.footnote = False 274 | if self.openFootnotes == 0: 275 | self._newline() 276 | 277 | def visit_label(self, node): 278 | self._add("^") 279 | self._add(node.astext()) 280 | self._add("^ ") 281 | 282 | raise nodes.SkipNode 283 | 284 | def depart_label(self, node): 285 | pass 286 | 287 | def visit_literal_block(self, node): 288 | self.element_level += 1 289 | self.keepLineBreaks = True 290 | self.inCode = True 291 | self._add('{code}') 292 | self._newline() 293 | 294 | def depart_literal_block(self, node): 295 | self.keepLineBreaks = False 296 | self.inCode = False 297 | self._newline() 298 | self._add('{code}') 299 | self._newline() 300 | self.element_level -= 1 301 | 302 | def visit_literal(self, node): 303 | self.element_level += 1 304 | self._add_space_when_needed() 305 | self._add('{{') 306 | 307 | def depart_literal(self, node): 308 | self._add('}}') 309 | self.element_level -= 1 310 | 311 | def visit_footer(self, node): 312 | pass 313 | 314 | # ---------------------------------------------------- 315 | 316 | # title 317 | def visit_title(self, node): 318 | self.element_level += 1 319 | if self.section_level == 0: 320 | self.section_level = 1 321 | if not self.first: 322 | self._newline() 323 | self._add("h" + str(self.section_level) + ". ") 324 | self.inTitle = True 325 | 326 | def depart_title(self, node): 327 | anchorname = self.cflAnchorValue(node.astext()).lower() 328 | self._add('{anchor:' + anchorname + '}') 329 | self._newline(2) 330 | self.first = True 331 | self.inTitle = False 332 | self.element_level -= 1 333 | 334 | def visit_subtitle(self, node): 335 | self.element_level += 1 336 | self._add("h" + str(self.section_level) + ". ") 337 | 338 | def depart_subtitle(self, node): 339 | self._newline(2) 340 | self.element_level -= 1 341 | 342 | # bullet list 343 | def visit_bullet_list(self, node): 344 | self.element_level += 1 345 | self.list_level += 1 346 | self.list_prefix[-1].append("*") 347 | 348 | def depart_bullet_list(self, node): 349 | self.list_level -= 1 350 | self.list_prefix[-1].pop() 351 | self.element_level -= 1 352 | 353 | def visit_list_item(self, node): 354 | self.element_level += 1 355 | self._add("".join(self.list_prefix[-1]) + " ") 356 | self.first = True 357 | self.lineBeginsWithListIndicator = True 358 | 359 | def depart_list_item(self, node): 360 | self.element_level -= 1 361 | 362 | # enumerated list 363 | def visit_enumerated_list(self, node): 364 | self.element_level += 1 365 | self.list_prefix[-1].append("#") 366 | self.list_counter = 1 367 | self.list_level += 1 368 | 369 | def depart_enumerated_list(self, node): 370 | self.list_counter = -1 371 | self.list_level -= 1 372 | self.list_prefix[-1].pop() 373 | self.element_level -= 1 374 | 375 | # admonitions 376 | def visit_info(self, node): 377 | self.element_level += 1 378 | self._add("{info}") 379 | self.do_visit_admonition() 380 | 381 | def depart_info(self, node): 382 | self._add("{info}") 383 | self.do_depart_admonition() 384 | -self.element_level 385 | 386 | def visit_note(self, node): 387 | self.element_level += 1 388 | self._add("{note}") 389 | self.do_visit_admonition() 390 | 391 | def depart_note(self, node): 392 | self._add("{note}") 393 | self.do_depart_admonition() 394 | self.element_level -= 1 395 | 396 | def visit_tip(self, node): 397 | self.element_level += 1 398 | self._add("{tip}") 399 | self.do_visit_admonition() 400 | 401 | def depart_tip(self, node): 402 | self._add("{tip}") 403 | self.do_depart_admonition() 404 | self.element_level -= 1 405 | 406 | def visit_docinfo(self, node): 407 | self.element_level += 1 408 | self.table = True 409 | self.docinfo = True 410 | 411 | def depart_docinfo(self, node): 412 | self.table = False 413 | self.docinfo = False 414 | self._newline(2) 415 | self.element_level -= 1 416 | 417 | def visit_meta(self, node): 418 | self.element_level += 1 419 | name = node.get('name') 420 | content = node.get('content') 421 | self.meta[name] = content 422 | 423 | def depart_meta(self, node): 424 | self.element_level -= 1 425 | 426 | def _docinfo_field(self, node): 427 | # non-standard docinfo field, becomes a generic field element. 428 | # and render as normal table fields. 429 | if self.docinfo: 430 | self._add("||%s|" % node.__class__.__name__) 431 | self.visit_field_body(node) 432 | 433 | def visit_author(self, node): 434 | return self._docinfo_field(node) 435 | 436 | def depart_author(self, node): 437 | if self.docinfo: 438 | self.depart_field_body(node) 439 | 440 | def visit_contact(self, node): 441 | return self._docinfo_field(node) 442 | 443 | def depart_contact(self, node): 444 | if self.docinfo: 445 | self.depart_field_body(node) 446 | 447 | def visit_date(self, node): 448 | return self._docinfo_field(node) 449 | 450 | def depart_date(self, node): 451 | if self.docinfo: 452 | self.depart_field_body(node) 453 | 454 | def visit_status(self, node): 455 | return self._docinfo_field(node) 456 | 457 | def depart_status(self, node): 458 | if self.docinfo: 459 | self.depart_field_body(node) 460 | 461 | def visit_version(self, node): 462 | return self._docinfo_field(node) 463 | 464 | def depart_version(self, node): 465 | if self.docinfo: 466 | self.depart_field_body(node) 467 | 468 | def visit_revision(self, node): 469 | return self._docinfo_field(node) 470 | 471 | def depart_revision(self, node): 472 | if self.docinfo: 473 | self.depart_field_body(node) 474 | 475 | def visit_inline(self, node): 476 | pass 477 | 478 | def depart_inline(self, node): 479 | pass 480 | 481 | def visit_warning(self, node): 482 | self.element_level += 1 483 | self._add("{warning}") 484 | self.do_visit_admonition() 485 | 486 | def depart_warning(self, node): 487 | self._add("{warning}") 488 | self.do_depart_admonition() 489 | self.element_level -= 1 490 | 491 | # admonition helpers 492 | def do_visit_admonition(self): 493 | self.element_level += 1 494 | self.list_prefix.append([]) 495 | 496 | def do_depart_admonition(self): 497 | self.list_prefix.pop() 498 | if self.list_level == 0: 499 | self._newline(2) 500 | else: 501 | self._newline() 502 | self.element_level -= 1 503 | 504 | # image 505 | def visit_image(self, node): 506 | self.element_level += 1 507 | if 'classes' in node: 508 | for classval in node['classes']: 509 | if classval.startswith("gallery-"): 510 | self._print_image_gallery(node, classval) 511 | return 512 | if self.figure: 513 | self.figureImage = node 514 | else: 515 | self._print_image(node) 516 | 517 | def depart_image(self, node): 518 | self.element_level -= 1 519 | 520 | def _print_image(self, node): 521 | uri = node['uri'] 522 | atts = {} 523 | if 'align' in node: 524 | atts['align'] = node['align'] 525 | if 'alt' in node: 526 | atts['alt'] = node['alt'] 527 | if 'height' in node: 528 | atts['height'] = node['height'] 529 | if 'scale' in node: 530 | # confluence has no percentages, so we simply make thumbnail 531 | atts['thumbnail'] = None 532 | if 'title' in node: 533 | atts['title'] = node['title'] 534 | if 'width' in node: 535 | atts['width'] = node['width'] 536 | attributes = [] 537 | for att in sorted(atts.keys()): 538 | if atts[att]: 539 | attributes.append(att + "=" + atts[att]) 540 | else: 541 | attributes.append(att) 542 | 543 | self._add("!") 544 | self._add(uri) 545 | if attributes: 546 | self._add("|") 547 | self._add(",".join(attributes)) 548 | self._add("!") 549 | self._newline() 550 | 551 | def _print_image_gallery(self, node, galleryclass): 552 | uri = node['uri'] 553 | if galleryclass == self.lastGalleryClass\ 554 | and self.content[-1].startswith("{gallery:"): 555 | self.content[-1] = self.content[-1][0:-2] + "," + uri + "}\n" 556 | else: 557 | self.lastGalleryClass = galleryclass 558 | self._add("{gallery:include=" + uri + "}\n") 559 | 560 | # figure 561 | def visit_figure(self, node): 562 | self.element_level += 1 563 | self.figure = True 564 | 565 | def depart_figure(self, node): 566 | if not self.figureImage: 567 | # happens in gallery mode 568 | return 569 | 570 | foo = vars(node)['attributes'] 571 | for att in foo: 572 | self.figureImage[att] = foo[att] 573 | 574 | self.figure = False 575 | self._print_image(self.figureImage) 576 | self.figureImage = None 577 | self.element_level -= 1 578 | 579 | def visit_caption(self, node): 580 | self.figureImage['title'] = node.children[0] 581 | raise nodes.SkipNode 582 | 583 | # table 584 | def visit_table(self, node): 585 | self.element_level += 1 586 | self.table = True 587 | # raise nodes.SkipNode 588 | 589 | def depart_table(self, node): 590 | self.table = False 591 | self._newline() 592 | self.element_level -= 1 593 | 594 | def visit_thead(self, node): 595 | self.element_level += 1 596 | # self._add("||") 597 | self.table_header = True 598 | 599 | def depart_thead(self, node): 600 | self.table_header = False 601 | self.element_level -= 1 602 | 603 | def visit_tbody(self, node): 604 | self.element_level += 1 605 | 606 | def depart_tbody(self, node): 607 | self.element_level -= 1 608 | 609 | def visit_row(self, node): 610 | self.element_level += 1 611 | 612 | def depart_row(self, node): 613 | if self.table_header: 614 | self._add("||") 615 | else: 616 | self._add("|") 617 | 618 | self._newline() 619 | self.element_level -= 1 620 | 621 | def visit_entry(self, node): 622 | self.element_level += 1 623 | if not self.table: 624 | return 625 | 626 | self.first = True 627 | self.tableEntryDiv = False 628 | 629 | if self.table_header: 630 | self._add("||") 631 | else: 632 | self._add("|") 633 | self.lastTableEntryBar = len(self.content) - 1 634 | 635 | def depart_entry(self, node): 636 | if not self.table: 637 | return 638 | 639 | self._remove_last_newline() 640 | self.first = False 641 | if self.tableEntryDiv: 642 | # work around bug in confluence 643 | # https://jira.atlassian.com/browse/CONF-9785 644 | self._add("{div}") 645 | self.element_level -= 1 646 | 647 | """Definition list 648 | Confluence wiki does not support definition list 649 | Definition list is converted to h6 section 650 | """ 651 | 652 | def visit_definition_list(self, node): 653 | pass 654 | 655 | def depart_definition_list(self, node): 656 | pass 657 | 658 | def visit_definition_list_item(self, node): 659 | self.has_classifier = False 660 | 661 | def depart_definition_list_item(self, node): 662 | pass 663 | 664 | def visit_term(self, node): 665 | self.element_level += 1 666 | self._add("h6. ") 667 | 668 | def depart_term(self, node): 669 | self._newline() 670 | self.element_level -= 1 671 | 672 | def visit_classifier(self, node): 673 | if self.has_classifier: 674 | self._add(", ") 675 | self._add("_") 676 | self.has_classifier = True 677 | 678 | def depart_classifier(self, node): 679 | self._add("_") 680 | 681 | def visit_definition(self, node): 682 | self.element_level += 1 683 | if self.has_classifier: 684 | self._newline() 685 | self.first = True 686 | 687 | def depart_definition(self, node): 688 | self._newline() 689 | self.element_level -= 1 690 | 691 | def visit_block_quote(self, nde): 692 | self.element_level += 1 693 | if self.quote_level == 0: 694 | self._add("{quote}") 695 | 696 | self.quote_level += 1 697 | 698 | def depart_block_quote(self, nde): 699 | if self.quote_level == 1: 700 | self._add("{quote}") 701 | self._newline() 702 | 703 | self.quote_level -= 1 704 | self.element_level -= 1 705 | 706 | def invisible_visit(self, node): 707 | """Invisible nodes should be ignored.""" 708 | raise nodes.SkipNode 709 | 710 | visit_comment = invisible_visit 711 | 712 | def visit_topic(self, node): 713 | self._add("{toc}") 714 | self._newline(2) 715 | raise nodes.SkipNode 716 | 717 | def visit_system_message(self, node): 718 | self.element_level += 1 719 | self._add( 720 | "{warning:title=System Message: %s/%s}" % 721 | (node['type'], node['level'])) 722 | self._newline() 723 | self._add('{{' + node['source'] + "}}#" + str(node['line'])) 724 | 725 | def depart_system_message(self, node): 726 | self._add("{warning}") 727 | self.element_level -= 1 728 | 729 | # field lists 730 | def visit_field_list(self, node): 731 | self.element_level += 1 732 | self._newline() 733 | 734 | def depart_field_list(self, node): 735 | self._newline() 736 | self.element_level -= 1 737 | 738 | def visit_field_name(self, node): 739 | self.element_level += 1 740 | self._add("||") 741 | 742 | def depart_field_name(self, node): 743 | self.element_level += 1 744 | 745 | def visit_field_body(self, node): 746 | self.element_level += 1 747 | self.field_body = True 748 | self._add("|") 749 | 750 | def depart_field_body(self, node): 751 | self.field_body = False 752 | self._add("|") 753 | self._newline() 754 | self.element_level -= 1 755 | 756 | # line blocks 757 | def visit_line_block(self, node): 758 | if not self.field_body: 759 | self._newline() 760 | 761 | def depart_line(self, node): 762 | self._newline() 763 | 764 | # roles http://docutils.sourceforge.net/docs/ref/rst/roles.html 765 | def visit_title_reference(self, node): 766 | self._add("_") 767 | 768 | def depart_title_reference(self, node): 769 | self._add("_") 770 | 771 | def _add_space_when_needed(self): 772 | if len(self.content) == 0: 773 | return 774 | lastline = self.content[len(self.content) - 1] 775 | if not lastline.endswith(" ") and not lastline.endswith("\n"): 776 | self._add(" ") 777 | 778 | def _endswidth(self, string): 779 | lastline = self.content[len(self.content) - 1] 780 | return lastline.endswith(string) 781 | 782 | # substitution definitions 783 | def visit_substitution_definition(self, node): 784 | raise nodes.SkipNode 785 | -------------------------------------------------------------------------------- /rst2tree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import codecs 5 | 6 | sys.stdout = codecs.getwriter('utf_8')(sys.stdout) 7 | 8 | txt = codecs.open(sys.argv[1], 'r', 'utf_8').read() 9 | 10 | 11 | def rst2tree(txt): 12 | import docutils.parsers.rst 13 | parser = docutils.parsers.rst.Parser() 14 | document = docutils.utils.new_document("test") 15 | document.settings.tab_width = 4 16 | document.settings.pep_references = 1 17 | document.settings.rfc_references = 1 18 | parser.parse(txt, document) 19 | return document 20 | 21 | 22 | doc = rst2tree(txt) 23 | 24 | print(doc.pformat(' ')) 25 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Runs rst2confluence.py on all .rst files in test/ and compares them 3 | # to the expected output (.exp) 4 | if [ $# -eq 0 ]; then 5 | files=test/*.rst 6 | else 7 | files="$*" 8 | fi 9 | 10 | for i in $files; do 11 | echo -n Testing $i 12 | expFile="$i.exp" 13 | outFile="$i.out" 14 | diffFile="$i.diff" 15 | 16 | options="" 17 | case $i in test/excerpt-enabled*) 18 | options="--excerpt" 19 | esac 20 | 21 | ./rst2confluence.py $options "$i" > "$outFile" 22 | if [ $? -ne 0 ]; then 23 | echo "\033[00;31merror running rst2confluence\033[00m" 24 | exit 1 25 | fi 26 | 27 | if [ ! -f "$expFile" ]; then 28 | expFile=/dev/null 29 | fi 30 | 31 | diff -u "$expFile" "$outFile" > "$diffFile" 32 | if [ "$?" -ne "0" ]; then 33 | echo " \033[00;31merror\033[00m" 34 | cat "$diffFile" | colordiff 35 | exit 2 36 | else 37 | #all fine 38 | echo " \033[00;32mok\033[00m" 39 | rm "$outFile" 40 | rm "$diffFile" 41 | fi 42 | done 43 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import shutil 4 | from distutils.core import setup 5 | 6 | if not os.path.exists('scripts'): 7 | os.makedirs('scripts') 8 | 9 | shutil.copyfile('rst2confluence.py', 'scripts/rst2confluence') 10 | 11 | setup( 12 | name='rst2confluence', 13 | version='0.6.0', 14 | description='reStructuredText-to-Confluence markup converter', 15 | 16 | author='Kenichiro TANAKA', 17 | author_email='tanaka.kenichiro@gmail.com', 18 | 19 | maintainer='Christian Weiske', 20 | maintainer_email='christian.weiske@netresearch.de', 21 | 22 | url='https://github.com/netresearch/rst2confluence', 23 | 24 | license='AGPL', 25 | 26 | py_modules=['rst2confluence.confluence'], 27 | # package_dir={'rst2confluence': 'src/rst2confluence'}, 28 | scripts=['scripts/rst2confluence'] 29 | ) 30 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.diff 2 | *.out 3 | *.html 4 | -------------------------------------------------------------------------------- /test/admonitions.rst: -------------------------------------------------------------------------------- 1 | .. note:: This is a note. 2 | This is the second line of the note paragraph. 3 | 4 | This is another paragaph in the note. 5 | 6 | .. warning:: This is a warning 7 | 8 | .. note:: This is a note 9 | 10 | .. tip:: This is a tip 11 | -------------------------------------------------------------------------------- /test/admonitions.rst.exp: -------------------------------------------------------------------------------- 1 | {note}This is a note. This is the second line of the note paragraph. 2 | 3 | This is another paragaph in the note. 4 | {note} 5 | 6 | {warning} 7 | This is a warning 8 | {warning} 9 | 10 | {note} 11 | This is a note 12 | {note} 13 | 14 | {tip} 15 | This is a tip 16 | {tip} 17 | 18 | -------------------------------------------------------------------------------- /test/block_quote.rst: -------------------------------------------------------------------------------- 1 | Block quotes are just: 2 | 3 | Indented paragraphs, 4 | 5 | and they may nest. 6 | -------------------------------------------------------------------------------- /test/block_quote.rst.exp: -------------------------------------------------------------------------------- 1 | Block quotes are just: 2 | {quote} 3 | Indented paragraphs, 4 | 5 | and they may nest. 6 | {quote} 7 | -------------------------------------------------------------------------------- /test/bug1-bold-sublist.rst: -------------------------------------------------------------------------------- 1 | main 2 | 3 | * **bold** 4 | 5 | * Sublist 6 | -------------------------------------------------------------------------------- /test/bug1-bold-sublist.rst.exp: -------------------------------------------------------------------------------- 1 | main 2 | {quote}* *bold* 3 | ** Sublist 4 | {quote} 5 | -------------------------------------------------------------------------------- /test/bug2-flowingtext-literal.rst: -------------------------------------------------------------------------------- 1 | This is a line that wraps onto the 2 | ``next`` line, with a literal text. 3 | -------------------------------------------------------------------------------- /test/bug2-flowingtext-literal.rst.exp: -------------------------------------------------------------------------------- 1 | This is a line that wraps onto the {{next}} line, with a literal text. 2 | -------------------------------------------------------------------------------- /test/bug3-note-indent.rst: -------------------------------------------------------------------------------- 1 | * bullet 2 | 3 | * sub-bullet 4 | 5 | .. note:: 6 | 7 | * Foo 8 | * Bar 9 | * Baz with some long text 10 | and a second line 11 | 12 | * sub-bullet 2 13 | 14 | * second bullet 15 | 16 | * another sub-bullet 17 | -------------------------------------------------------------------------------- /test/bug3-note-indent.rst.exp: -------------------------------------------------------------------------------- 1 | * bullet 2 | ** sub-bullet 3 | {note}* Foo 4 | * Bar 5 | * Baz with some long text and a second line 6 | {note} 7 | ** sub-bullet 2 8 | * second bullet 9 | ** another sub-bullet 10 | -------------------------------------------------------------------------------- /test/bug4-only-bold.rst: -------------------------------------------------------------------------------- 1 | **foo** 2 | -------------------------------------------------------------------------------- /test/bug4-only-bold.rst.exp: -------------------------------------------------------------------------------- 1 | *foo* 2 | -------------------------------------------------------------------------------- /test/bullet_list.rst: -------------------------------------------------------------------------------- 1 | * item1 2 | * item2 3 | 4 | * item2-1 5 | * item2-2 6 | 7 | * item2-2-1 8 | * item2-2-2 9 | 10 | * item3 11 | -------------------------------------------------------------------------------- /test/bullet_list.rst.exp: -------------------------------------------------------------------------------- 1 | * item1 2 | * item2 3 | ** item2-1 4 | ** item2-2 5 | *** item2-2-1 6 | *** item2-2-2 7 | * item3 8 | -------------------------------------------------------------------------------- /test/bullet_list_paragraphs.rst: -------------------------------------------------------------------------------- 1 | - item 1 2 | - item 2 3 | 4 | item 2, paragraph 2 5 | - item 3 6 | 7 | item 3, p2 8 | 9 | - item 4 10 | 11 | item 4, p2 12 | 13 | - item 4.1 14 | - item 4.2 15 | 16 | item 4.2, p2 17 | 18 | - item 4.3 19 | 20 | item 4.3, p2 21 | 22 | item 4.3, p3 23 | - item 4.4 24 | 25 | - item 5 26 | -------------------------------------------------------------------------------- /test/bullet_list_paragraphs.rst.exp: -------------------------------------------------------------------------------- 1 | * item 1 2 | * item 2 3 | item 2, paragraph 2 4 | * item 3 5 | item 3, p2 6 | * item 4 7 | item 4, p2 8 | ** item 4.1 9 | ** item 4.2 10 | item 4.2, p2 11 | ** item 4.3 12 | item 4.3, p2 13 | item 4.3, p3 14 | ** item 4.4 15 | * item 5 16 | -------------------------------------------------------------------------------- /test/bullet_list_table.rst: -------------------------------------------------------------------------------- 1 | +-----+---------------+ 2 | | Foo | Nested list: | 3 | | | | 4 | | | - One | 5 | | | | 6 | | | - One sub | 7 | | | - One sub 2 | 8 | | | - Two | 9 | | | | 10 | | | - Two sub | 11 | +-----+---------------+ 12 | | Bar | Paragraphs: | 13 | | | | 14 | | | - One | 15 | | | | 16 | | | Second: | 17 | | | | 18 | | | - One | 19 | | | | 20 | | | - One sub | 21 | | | - One sub 2 | 22 | | | - Two | 23 | | | | 24 | | | - Two sub | 25 | +-----+---------------+ 26 | 27 | .. see https://jira.atlassian.com/browse/CONF-9785 28 | -------------------------------------------------------------------------------- /test/bullet_list_table.rst.exp: -------------------------------------------------------------------------------- 1 | |Foo|Nested list: 2 | * One 3 | ** One sub 4 | ** One sub 2 5 | * Two 6 | ** Two sub| 7 | |Bar|{div}Paragraphs: 8 | * One 9 | 10 | Second: 11 | * One 12 | ** One sub 13 | ** One sub 2 14 | * Two 15 | ** Two sub{div}| 16 | 17 | -------------------------------------------------------------------------------- /test/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netresearch/rst2confluence/c8296828b53adec1094871cb28723bb34d87df02/test/cat.jpg -------------------------------------------------------------------------------- /test/definition_list.rst: -------------------------------------------------------------------------------- 1 | term 1 2 | Definition 1. 3 | 4 | term 2 5 | Definition 2, paragraph 1. 6 | 7 | Definition 2, paragraph 2. 8 | 9 | term 3 : classifier 10 | Definition 3. 11 | 12 | term 4 : classifier one : classifier two 13 | Definition 4. 14 | -------------------------------------------------------------------------------- /test/definition_list.rst.exp: -------------------------------------------------------------------------------- 1 | h6. term 1 2 | Definition 1. 3 | 4 | h6. term 2 5 | Definition 2, paragraph 1. 6 | 7 | Definition 2, paragraph 2. 8 | 9 | h6. term 3 10 | _classifier_ 11 | Definition 3. 12 | 13 | h6. term 4 14 | _classifier one_, _classifier two_ 15 | Definition 4. 16 | 17 | -------------------------------------------------------------------------------- /test/docinfo.rst: -------------------------------------------------------------------------------- 1 | Foo 2 | === 3 | 4 | 5 | :Author: Foo Bar 6 | :Contact: foo.bar@baz.org 7 | :Date: $Date: Mon, 09 Dec 2013 10:54:14 GMT $ 8 | :Status: Work In Progress 9 | :Version: 1 10 | :Revision: 01 11 | :Filename: $RCSfile$ 12 | 13 | .. meta:: 14 | 15 | :title: foo document 16 | :keywords: foo, bar 17 | -------------------------------------------------------------------------------- /test/docinfo.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Foo{anchor:foo} 2 | 3 | ||author||Foo Bar| 4 | ||contact||[foo.bar@baz.org|mailto:foo.bar@baz.org]| 5 | ||date||Mon, 09 Dec 2013 10:54:14 GMT| 6 | ||status||Work In Progress| 7 | ||version||1| 8 | ||revision||01| 9 | ||Filename|$RCSfile$| 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/enumerated_list.rst: -------------------------------------------------------------------------------- 1 | #. item1 2 | #. item2 3 | 4 | #. item2-1 5 | #. item2-2 6 | 7 | #. item3 8 | 9 | * item3-1 10 | * item3-2 11 | 12 | * item4 13 | 14 | #. item4-1 15 | #. item4-2 16 | -------------------------------------------------------------------------------- /test/enumerated_list.rst.exp: -------------------------------------------------------------------------------- 1 | # item1 2 | # item2 3 | ## item2-1 4 | ## item2-2 5 | # item3 6 | #* item3-1 7 | #* item3-2 8 | * item4 9 | *# item4-1 10 | *# item4-2 11 | -------------------------------------------------------------------------------- /test/escaping.rst: -------------------------------------------------------------------------------- 1 | Brackets get escaped: [Foo] 2 | 3 | Not within code, though:: 4 | 5 | This [does] not get escaped 6 | -------------------------------------------------------------------------------- /test/escaping.rst.exp: -------------------------------------------------------------------------------- 1 | Brackets get escaped: \[Foo] 2 | 3 | Not within code, though: 4 | {code} 5 | This [does] not get escaped 6 | {code} 7 | -------------------------------------------------------------------------------- /test/excerpt-disabled.rst: -------------------------------------------------------------------------------- 1 | Title 2 | ===== 3 | This is the first paragraph. 4 | 5 | This is the second paragraph. 6 | -------------------------------------------------------------------------------- /test/excerpt-disabled.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Title{anchor:title} 2 | 3 | This is the first paragraph. 4 | 5 | This is the second paragraph. 6 | -------------------------------------------------------------------------------- /test/excerpt-enabled-complex.rst: -------------------------------------------------------------------------------- 1 | Complex excerpt test 2 | ==================== 3 | 4 | .. contents:: 5 | 6 | Lists 7 | ----- 8 | 9 | * A list 10 | * Another item 11 | 12 | #. Enumerated 13 | #. List 14 | 15 | 16 | Definition: 17 | List 18 | Definition2: 19 | List 20 | 21 | Table 22 | ----- 23 | ===== ===== 24 | A Table 25 | ===== ===== 26 | With some 27 | cells here 28 | ===== ===== 29 | 30 | Misc 31 | ---- 32 | :: 33 | 34 | Literal block 35 | 36 | Admonitions 37 | ----------- 38 | .. note:: This is a note. 39 | This is the second line of the note paragraph. 40 | 41 | This is another paragaph in the note. 42 | 43 | .. warning:: This is a warning 44 | 45 | .. note:: This is a note 46 | 47 | .. tip:: This is a tip 48 | 49 | 50 | This is the first real standalone paragraph. 51 | 52 | All other elements before influence the element_level count, 53 | so having them before the paragraph tests the code quite good. 54 | -------------------------------------------------------------------------------- /test/excerpt-enabled-complex.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Complex excerpt test{anchor:complexexcerpttest} 2 | 3 | {toc} 4 | 5 | h2. Lists{anchor:lists} 6 | 7 | * A list 8 | * Another item 9 | # Enumerated 10 | # List 11 | h6. Definition: 12 | List 13 | 14 | h6. Definition2: 15 | List 16 | 17 | 18 | h2. Table{anchor:table} 19 | 20 | ||A||Table|| 21 | |With|some| 22 | |cells|here| 23 | 24 | 25 | h2. Misc{anchor:misc} 26 | 27 | {code} 28 | Literal block 29 | {code} 30 | h2. Admonitions{anchor:admonitions} 31 | 32 | {note}This is a note. This is the second line of the note paragraph. 33 | 34 | This is another paragaph in the note. 35 | {note} 36 | 37 | {warning} 38 | This is a warning 39 | {warning} 40 | 41 | {note} 42 | This is a note 43 | {note} 44 | 45 | {tip} 46 | This is a tip 47 | {tip} 48 | 49 | {excerpt} 50 | This is the first real standalone paragraph.{excerpt} 51 | 52 | All other elements before influence the element_level count, so having them before the paragraph tests the code quite good. 53 | -------------------------------------------------------------------------------- /test/excerpt-enabled.rst: -------------------------------------------------------------------------------- 1 | Title 2 | ===== 3 | This is the first paragraph. 4 | 5 | This is the second paragraph. 6 | -------------------------------------------------------------------------------- /test/excerpt-enabled.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Title{anchor:title} 2 | 3 | {excerpt}This is the first paragraph.{excerpt} 4 | 5 | This is the second paragraph. 6 | -------------------------------------------------------------------------------- /test/field_lists.rst: -------------------------------------------------------------------------------- 1 | We have a non-docinfo field list here: 2 | 3 | :Version: 1.23.42 4 | :Company: Foo Ltd. 5 | :Authors: - John Doe 6 | - Jane Smith 7 | :Dependencies: - foo 8 | - bar 9 | -------------------------------------------------------------------------------- /test/field_lists.rst.exp: -------------------------------------------------------------------------------- 1 | We have a non-docinfo field list here: 2 | 3 | ||Version|1.23.42| 4 | ||Company|Foo Ltd.| 5 | ||Authors|* John Doe 6 | * Jane Smith 7 | | 8 | ||Dependencies|* foo 9 | * bar 10 | | 11 | 12 | -------------------------------------------------------------------------------- /test/figure.rst: -------------------------------------------------------------------------------- 1 | .. figure:: cat.jpg 2 | :align: right 3 | :scale: 75% 4 | 5 | Example picture with a cat 6 | -------------------------------------------------------------------------------- /test/figure.rst.exp: -------------------------------------------------------------------------------- 1 | !cat.jpg|align=right,thumbnail,title=Example picture with a cat! 2 | -------------------------------------------------------------------------------- /test/footnote-auto.rst: -------------------------------------------------------------------------------- 1 | [#]_ is a reference to footnote 1, and [#]_ is a reference to footnote 2 2 | 3 | Text after the footnote paragraph. 4 | 5 | .. [#] Footnote text 1 6 | .. [#] Footnote text 2 7 | -------------------------------------------------------------------------------- /test/footnote-auto.rst.exp: -------------------------------------------------------------------------------- 1 | ^1^ is a reference to footnote 1, and ^2^ is a reference to footnote 2 2 | 3 | Text after the footnote paragraph. 4 | 5 | bq. ^1^ Footnote text 1 6 | bq. ^2^ Footnote text 2 7 | -------------------------------------------------------------------------------- /test/footnote-manual.rst: -------------------------------------------------------------------------------- 1 | [2]_ is a reference to footnote 2, and [1]_ is a reference to footnote 1 2 | 3 | .. [1] Footnote text 1 4 | .. [2] Footnote text 2 5 | -------------------------------------------------------------------------------- /test/footnote-manual.rst.exp: -------------------------------------------------------------------------------- 1 | ^2^ is a reference to footnote 2, and ^1^ is a reference to footnote 1 2 | 3 | bq. ^1^ Footnote text 1 4 | bq. ^2^ Footnote text 2 5 | -------------------------------------------------------------------------------- /test/footnote-mixed.rst: -------------------------------------------------------------------------------- 1 | [2]_ will be "2" (manually numbered), 2 | [#]_ will be "3" (anonymous auto-numbered), and 3 | [#label]_ will be "1" (labeled auto-numbered). 4 | 5 | .. [2] This footnote is labeled manually, so its number is fixed. 6 | 7 | .. [#label] This autonumber-labeled footnote will be labeled "1". 8 | It is the first auto-numbered footnote and no other footnote 9 | with label "1" exists. The order of the footnotes is used to 10 | determine numbering, not the order of the footnote references. 11 | 12 | .. [#] This footnote will be labeled "3". It is the second 13 | auto-numbered footnote, but footnote label "2" is already used. 14 | -------------------------------------------------------------------------------- /test/footnote-mixed.rst.exp: -------------------------------------------------------------------------------- 1 | ^2^ will be "2" (manually numbered), ^3^ will be "3" (anonymous auto-numbered), and ^1^ will be "1" (labeled auto-numbered). 2 | 3 | bq. ^2^ This footnote is labeled manually, so its number is fixed. 4 | bq. ^1^ This autonumber-labeled footnote will be labeled "1". It is the first auto-numbered footnote and no other footnote with label "1" exists. The order of the footnotes is used to determine numbering, not the order of the footnote references. 5 | bq. ^3^ This footnote will be labeled "3". It is the second auto-numbered footnote, but footnote label "2" is already used. 6 | -------------------------------------------------------------------------------- /test/gridtable.rst: -------------------------------------------------------------------------------- 1 | +------+------+-------+ 2 | | Foo | Bar | Baz | 3 | +======+======+=======+ 4 | | One | Two | Three | 5 | +------+------+-------+ 6 | | Four | Five | Six | 7 | +------+------+-------+ 8 | -------------------------------------------------------------------------------- /test/gridtable.rst.exp: -------------------------------------------------------------------------------- 1 | ||Foo||Bar||Baz|| 2 | |One|Two|Three| 3 | |Four|Five|Six| 4 | 5 | -------------------------------------------------------------------------------- /test/image-gallery.rst: -------------------------------------------------------------------------------- 1 | Class names beginning with "gallery-" form a gallery. 2 | 3 | .. image:: cat.jpg 4 | :class: gallery-1 5 | 6 | .. image:: dog.jpg 7 | :class: gallery-1 8 | 9 | .. image:: cat.jpg 10 | :class: gallery-2 11 | :scale: 50% 12 | 13 | Some text. The next images starts a fresh gallery, despite the already 14 | used class. 15 | 16 | .. image:: cat.jpg 17 | :class: gallery-2 18 | 19 | Figures are supported, too: 20 | 21 | .. figure:: cat.jpg 22 | :class: gallery-1 23 | 24 | .. figure:: dog.jpg 25 | :class: gallery-1 26 | -------------------------------------------------------------------------------- /test/image-gallery.rst.exp: -------------------------------------------------------------------------------- 1 | Class names beginning with "gallery-" form a gallery. 2 | {gallery:include=cat.jpg,dog.jpg} 3 | {gallery:include=cat.jpg} 4 | 5 | Some text. The next images starts a fresh gallery, despite the already used class. 6 | {gallery:include=cat.jpg} 7 | 8 | Figures are supported, too: 9 | {gallery:include=cat.jpg,dog.jpg} 10 | -------------------------------------------------------------------------------- /test/image.rst: -------------------------------------------------------------------------------- 1 | .. image:: picture1.jpeg 2 | :height: 100px 3 | :width: 200 px 4 | :alt: alternate text 5 | :align: right 6 | 7 | .. image:: picture1.jpeg 8 | :scale: 50 % 9 | 10 | .. image:: http://www.jus.co.jp/picture2.jpeg 11 | 12 | .. image:: images/picture3.jpeg 13 | -------------------------------------------------------------------------------- /test/image.rst.exp: -------------------------------------------------------------------------------- 1 | !picture1.jpeg|align=right,alt=alternate text,height=100px,width=200px! 2 | !picture1.jpeg|thumbnail! 3 | !http://www.jus.co.jp/picture2.jpeg! 4 | !images/picture3.jpeg! 5 | -------------------------------------------------------------------------------- /test/line-blocks-in-a-field-list.rst: -------------------------------------------------------------------------------- 1 | Line block in a field list 2 | 3 | :Version: 1.2.3 4 | :Author: | Foo 5 | | Bar 6 | | Baz 7 | :State: alpha 8 | -------------------------------------------------------------------------------- /test/line-blocks-in-a-field-list.rst.exp: -------------------------------------------------------------------------------- 1 | Line block in a field list 2 | 3 | ||Version|1.2.3| 4 | ||Author|Foo 5 | Bar 6 | Baz 7 | | 8 | ||State|alpha| 9 | 10 | -------------------------------------------------------------------------------- /test/line-blocks.rst: -------------------------------------------------------------------------------- 1 | This is a normal paragraph 2 | on several lines. 3 | 4 | | First line 5 | | Second line 6 | 7 | | Continuation 8 | on the next line 9 | | End. 10 | 11 | This is a normal paragraph 12 | -------------------------------------------------------------------------------- /test/line-blocks.rst.exp: -------------------------------------------------------------------------------- 1 | This is a normal paragraph on several lines. 2 | 3 | First line 4 | Second line 5 | 6 | Continuation on the next line 7 | End. 8 | 9 | This is a normal paragraph 10 | -------------------------------------------------------------------------------- /test/linebreak-bold.rst: -------------------------------------------------------------------------------- 1 | We do this and that, and 2 | **then** we do the other thing. 3 | -------------------------------------------------------------------------------- /test/linebreak-bold.rst.exp: -------------------------------------------------------------------------------- 1 | We do this and that, and *then* we do the other thing. 2 | -------------------------------------------------------------------------------- /test/linebreak.rst: -------------------------------------------------------------------------------- 1 | line1 2 | line2 3 | 4 | line3 5 | line4 6 | -------------------------------------------------------------------------------- /test/linebreak.rst.exp: -------------------------------------------------------------------------------- 1 | line1 line2 2 | 3 | line3 line4 4 | -------------------------------------------------------------------------------- /test/links.rst: -------------------------------------------------------------------------------- 1 | Link test 2 | ========= 3 | URL 4 | --- 5 | http://example.org/foo/bar?baz=boo 6 | 7 | http://example.org/?foo[bar]=baz 8 | 9 | External 10 | -------- 11 | This is `an external link`_. 12 | 13 | .. _an external link: http://example.org/external 14 | 15 | Embedded 16 | -------- 17 | See the `Python home page `_ for info. 18 | 19 | And with `special chars `_. 20 | 21 | Some `internal_page `_ page. 22 | 23 | Embedded internal 24 | ----------------- 25 | Link to a page with spaces: `Foo `_. 26 | 27 | Link to section 28 | --------------- 29 | See ``reference.rst`` for internal links. 30 | -------------------------------------------------------------------------------- /test/links.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Link test{anchor:linktest} 2 | 3 | h2. URL{anchor:url} 4 | 5 | http://example.org/foo/bar?baz=boo 6 | 7 | http://example.org/?foo\[bar\]=baz 8 | 9 | h2. External{anchor:external} 10 | 11 | This is [an external link|http://example.org/external]. 12 | 13 | h2. Embedded{anchor:embedded} 14 | 15 | See the [Python home page|http://www.python.org] for info. 16 | 17 | And with [special chars|http://example.org/?foo\[bar\]=baz]. 18 | 19 | Some [internal_page|internal_page] page. 20 | 21 | h2. Embedded internal{anchor:embeddedinternal} 22 | 23 | Link to a page with spaces: [Foo|Foo Bar]. 24 | 25 | h2. Link to section{anchor:linktosection} 26 | 27 | See {{reference.rst}} for internal links. 28 | -------------------------------------------------------------------------------- /test/literal.rst: -------------------------------------------------------------------------------- 1 | Literal ``words`` of text. 2 | 3 | We may have code blocks, too:: 4 | 5 | 10 | 11 | This is outside the block. 12 | 13 | Here is a ``{variable}`` in a literal block. 14 | 15 | Code with curly braces:: 16 | 17 | {this} is {{{really}}} {{curly}}! 18 | -------------------------------------------------------------------------------- /test/literal.rst.exp: -------------------------------------------------------------------------------- 1 | Literal {{words}} of text. 2 | 3 | We may have code blocks, too: 4 | {code} 5 | 10 | {code} 11 | 12 | This is outside the block. 13 | 14 | Here is a {{{variable}}} in a literal block. 15 | 16 | Code with curly braces: 17 | {code} 18 | {this} is {{{really}}} {{curly}}! 19 | {code} 20 | -------------------------------------------------------------------------------- /test/paragraph.rst: -------------------------------------------------------------------------------- 1 | .. note:: 2 | This is a note. 3 | 4 | .. warning:: 5 | This is a warning. -------------------------------------------------------------------------------- /test/paragraph.rst.exp: -------------------------------------------------------------------------------- 1 | {note}This is a note. 2 | {note} 3 | 4 | {warning} 5 | This is a warning. 6 | {warning} 7 | 8 | -------------------------------------------------------------------------------- /test/reference.rst: -------------------------------------------------------------------------------- 1 | Section to cross-reference 2 | -------------------------- 3 | 4 | .. _my-reference-label: 5 | 6 | This is the text of the section. 7 | 8 | It refers to the section itself, see my-reference-label_ . 9 | 10 | Section with än Umlaut 11 | ---------------------- 12 | Link to `Section with än Umlaut`_. 13 | -------------------------------------------------------------------------------- /test/reference.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Section to cross-reference{anchor:sectiontocrossreference} 2 | 3 | {anchor:myreferencelabel} 4 | This is the text of the section. 5 | 6 | It refers to the section itself, see [my-reference-label|#myreferencelabel] . 7 | 8 | h1. Section with än Umlaut{anchor:sectionwithanumlaut} 9 | 10 | Link to [Section with än Umlaut|#sectionwithanumlaut]. 11 | -------------------------------------------------------------------------------- /test/roles-title-reference.rst: -------------------------------------------------------------------------------- 1 | This is `interpreted text` using the default role. 2 | 3 | We had a :title-reference:`foo` and a :t:`bar`. 4 | -------------------------------------------------------------------------------- /test/roles-title-reference.rst.exp: -------------------------------------------------------------------------------- 1 | This is _interpreted text_ using the default role. 2 | 3 | We had a _foo_ and a _bar_. 4 | -------------------------------------------------------------------------------- /test/section-h1.rst: -------------------------------------------------------------------------------- 1 | Section 1 2 | ********* 3 | Section 2-1 4 | =========== 5 | Section 3 6 | --------- 7 | Section 2-2 8 | =========== 9 | -------------------------------------------------------------------------------- /test/section-h1.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Section 1{anchor:section1} 2 | 3 | h2. Section 2-1{anchor:section21} 4 | 5 | h3. Section 3{anchor:section3} 6 | 7 | h2. Section 2-2{anchor:section22} 8 | 9 | -------------------------------------------------------------------------------- /test/section.rst: -------------------------------------------------------------------------------- 1 | Section1 2 | ======================= 3 | 4 | Section1-1 5 | ----------------------- 6 | 7 | Section1-2 8 | ~~~~~~~~~~~~~~~~~~~~~~~ 9 | 10 | Section2 11 | ======================= 12 | 13 | -------------------------------------------------------------------------------- /test/section.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Section1{anchor:section1} 2 | 3 | h2. Section1-1{anchor:section11} 4 | 5 | h3. Section1-2{anchor:section12} 6 | 7 | h1. Section2{anchor:section2} 8 | 9 | -------------------------------------------------------------------------------- /test/section_reference.rst: -------------------------------------------------------------------------------- 1 | Section1 2 | ==================== 3 | 4 | Section1Section1 5 | 6 | Section2 7 | ==================== 8 | 9 | Reference to `Section1`_ -------------------------------------------------------------------------------- /test/section_reference.rst.exp: -------------------------------------------------------------------------------- 1 | h1. Section1{anchor:section1} 2 | 3 | Section1Section1 4 | 5 | h1. Section2{anchor:section2} 6 | 7 | Reference to [Section1|#section1] 8 | -------------------------------------------------------------------------------- /test/simpletable.rst: -------------------------------------------------------------------------------- 1 | ===== ===== ====== 2 | A B A or B 3 | ===== ===== ====== 4 | False False False 5 | True False True 6 | False True True 7 | True True True 8 | ===== ===== ====== 9 | 10 | 11 | ===== ===== ====== 12 | Inputs Output 13 | ------------ ------ 14 | A B A or B 15 | ===== ===== ====== 16 | False False False 17 | True False True 18 | False True True 19 | True True True 20 | ===== ===== ====== 21 | 22 | ===== ====================== 23 | Title URL 24 | ===== ====================== 25 | Foo http://example.org/foo 26 | ===== ====================== 27 | -------------------------------------------------------------------------------- /test/simpletable.rst.exp: -------------------------------------------------------------------------------- 1 | ||A||B||A or B|| 2 | |False|False|False| 3 | |True|False|True| 4 | |False|True|True| 5 | |True|True|True| 6 | 7 | ||Inputs||Output|| 8 | ||A||B||A or B|| 9 | |False|False|False| 10 | |True|False|True| 11 | |False|True|True| 12 | |True|True|True| 13 | 14 | ||Title||URL|| 15 | |Foo| http://example.org/foo| 16 | 17 | -------------------------------------------------------------------------------- /test/substitution.rst: -------------------------------------------------------------------------------- 1 | .. |text| replace:: substitution text 2 | 3 | This is some |text|. 4 | -------------------------------------------------------------------------------- /test/substitution.rst.exp: -------------------------------------------------------------------------------- 1 | This is some substitution text. 2 | -------------------------------------------------------------------------------- /test/text_effects.rst: -------------------------------------------------------------------------------- 1 | *emphasis* 2 | 3 | **strong emphasis** -------------------------------------------------------------------------------- /test/text_effects.rst.exp: -------------------------------------------------------------------------------- 1 | _emphasis_ 2 | 3 | *strong emphasis* 4 | -------------------------------------------------------------------------------- /test/toc.rst: -------------------------------------------------------------------------------- 1 | 2 | .. contents:: Table of contents 3 | 4 | Section 5 | ======= 6 | 7 | Subsection 8 | ---------- 9 | 10 | Section 2 11 | ========= 12 | -------------------------------------------------------------------------------- /test/toc.rst.exp: -------------------------------------------------------------------------------- 1 | {toc} 2 | 3 | h1. Section{anchor:section} 4 | 5 | h2. Subsection{anchor:subsection} 6 | 7 | h1. Section 2{anchor:section2} 8 | 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | #ignore = E501 3 | --------------------------------------------------------------------------------