Giter Club home page Giter Club logo

md2cf's Introduction

md2cf

md2cf is a tool and library that allows you to convert Markdown documents to Confluence Storage format and upload them to a Confluence instance.

Features

  • Convert Markdown documents: md2cf includes a library that implements a Mistune renderer, which outputs Confluence Storage Format.
  • Talk to the Confluence API: md2cf also features an embedded micro-implementation of the Confluence Server REST API with basic support for creating and updating pages and attachments.
  • Automate the upload process: You can use md2cf's full-featured command line utility to automate the upload process for you.

Installation

# Install md2cf via pip
pip install md2cf

# If you only need to use md2cf for uploading documents to Confluence,
# it's recommended to use pipx:
pipx install md2cf

Getting started

To see all available options and parameters, run md2cf --help.

To upload a document, you need to provide at least the following five parameters:

  • The URL of your Confluence instance, including the path to the REST API (e.g., http://confluence.example.com/rest/api)
  • Either:
    • The username and password to log in to the instance
    • A personal access token
  • The space in which to publish the page
  • The files or directories to be uploaded. If none are specified, the contents will be read from standard input.

Example basic usage:

md2cf --host 'https://confluence.example.com/rest/api' --username foo --password bar --space TEST document.md

Or, if using a token:

md2cf --host 'https://confluence.example.com/rest/api' --token '2104v3ryl0ngt0k3n720' --space TEST document.md

⚠️ Avoid entering your password (or token) as a command line parameter, as this is generally a bad practice. Instead, when running the script interactively, omit the --password parameter and securely enter the password when prompted.

⚠️ Note that tokens function differently between Confluence Cloud and self-hosted instances. When using Confluence Cloud, you should use your token as your password with the --username and --password parameters. With self-hosted instances, use the --token parameter instead.

You can also supply the hostname, username, password, token, and space as environment variables:

  • CONFLUENCE_HOST
  • CONFLUENCE_USERNAME
  • CONFLUENCE_PASSWORD
  • CONFLUENCE_TOKEN
  • CONFLUENCE_SPACE

If you are using self-signed certificates or want to ignore SSL errors, use the --insecure option.

You can upload multiple files or entire folders. If you specify a folder, md2cf will traverse it recursively and upload all files that end in .md. For more information, see Uploading Folders.

If you would like to preview md2cf's actions without modifying Confluence, use the --dry-run option. This will print a list of page data without making any changes.

Page information arguments

Page title

The title of the page can be sourced from multiple places, in the following order of priority:

  • The --title command-line parameter

  • A title entry in the document's front matter, which is a YAML block delimited by --- lines at the top of the file:

    ---
    title: This is a title
    ---
  • The first top-level header found in the document (i.e., the first # header)

  • The filename, if there are no top-level headers.

Note that if you are reading from standard input, you must either specify the title through the command line or include a title as a header or in the front matter within the content.

To avoid repeating the top level header in the body of the page, pass the --strip-top-header parameter to strip it from the document.

When uploading entire folders, consider adding a prefix to each page title to prevent collisions. You can do this by using the --prefix parameter.

Removing extra newlines

If your document uses single newlines to break lines (for example, if it was typeset with a fixed column width), Confluence Cloud might preserve those newlines, resulting in a document that's difficult to read. To fix this, use the --remove-text-newlines parameter, which replaces every newline within a paragraph with a space.

Example For example, this will turn
This is a document
with hardcoded newlines
in its paragraphs.

It's not that nice
to read.

into

This is a document with hardcoded newlines in its paragraphs.

It's not that nice to read.

Adding a preface and/or postface

The --preface-markdown, --preface-file, --postface-markdown, and --postface-file commands enable you to add text at the beginning or end of each page. This is especially helpful if you're mirroring documentation to Confluence and want to notify users that it will be automatically updated.

The --preface-markdown and --postface-markdown options allow you to specify Markdown text directly in the command line. If no text is specified, a default paragraph will be used stating:

Contents are auto-generated, do not edit.

Alternatively, the --preface-file and --postface-file options allow you to specify a path to a markdown file which will be prepended or appended to every page.

⚠️ Note that preface and postface Markdown is parsed separately and added to the body after the main page has been parsed. Therefore, it will not affect behavior tied to the page contents, such as title or front matter detection.

Page labels

To add labels to your page, include a labels entry in your document's front matter. The front matter is a YAML block delimited by --- lines at the top of the file. Here's an example:

---
labels:
- first label
- second label
---
# Rest of the Markdown document

Note that by default, the labels you specify will be added to any existing labels. If you want to replace all existing labels with only the ones you specified, use the --replace-all-labels option.

Parent page

To upload the page under a specific parent, you can provide the parent's page ID using the --parent-id parameter, or its title using the --parent-title parameter.

To move a page to a top-level page (i.e. directly under the space's Home Page), use the --top-level flag.

Update message

Optionally, you can provide an update message using the --message parameter to describe the change you made. If you're using the --only-changed option at the same time, the version update message will also include a hash of the page or attachment contents at the end.

Updating an existing page

If you upload a page with the same title twice, it will update the existing page.

To update a page using its ID, use the --page-id option. This allows you to modify the page's title or update a page with a title that is difficult to use as a parameter.

To avoid sending notifications to page watchers, use the --minor-edit option. This corresponds to the "Notify watchers" checkbox when editing pages manually.

Avoiding uploading content that hasn't changed

To avoid re-uploading unchanged content and receiving update emails when there are no changes, consider using the --only-changed option. Keep in mind that this option will include a hash of the page or attachment contents in the version update message.

Linking to other documents (relative links)

By default, support for relative links is disabled. To enable it, pass the --enable-relative-links flag. The behavior of relative links is similar to GitHub relative links, with the exception that links starting with / are not supported and will be left unchanged.

Reference to a section from another file is possible using Markdown fragment link navigation: [link](./file.md#section-name) // note the dash!

In file.md:

## ...
## section name

⚠️ Enabling this function requires two uploads for every page containing relative links. First, a page must be uploaded to Confluence with all internal links replaced by placeholders. Then, once the final Confluence link is known, the placeholders will be replaced with the appropriate links.

By default, relative links that point to non-existent files (or files that are not being uploaded in the current batch) will result in an error. To ignore these errors and keep the links as they are, use the --ignore-relative-link-errors flag.

Directory arguments

Uploading Folders Recursively

To help you mirror large documentation to Confluence, md2cf allows you to upload entire folders. When using this feature, md2cf will recursively traverse all subdirectories and upload any .md files it encounters.

By default, md2cf will respect your .gitignore file and skip any files or folders it defines. If you prefer to upload everything in the folder, use the --no-gitignore option.

Please note that Confluence can only nest pages under other pages. As a result, folders will be represented by empty pages with the same title as the folder in the final upload. You can customize this behavior using one of the three command line parameters defined in the next sections.

Customizing folder names

Folder names like interesting-subsection or dir1 are not particularly nice. If you pass the --beautify-folders option, all spaces and hyphens in folder names will be replaced with spaces and the first letter will be capitalized, producing Interesting subsection and Dir1.

Alternatively, you can create a YAML file called .pages with the following format in every folder you wish to rename. If you pass the --use-pages-file, the folder will be given that title.

Folder names like interesting-subsection or dir1 may not be aesthetically pleasing. If you use the --beautify-folders option, spaces and hyphens in folder names will be replaced with spaces, and the first letter of each word will be capitalized, resulting in Interesting Subsection and Dir1.

Alternatively, you can create a YAML file called .pages in every folder you want to rename, using the following format, and if you use the --use-pages-file option, the folder will be renamed using the title specified in the .pages file.

title: "This is a fantastic title!"

Collapse single pages

You can collapse directories that only contain one document by passing the --collapse-single-pages parameter.

Example This means that a folder layout like this:
document.md
folder1/
  documentA.md
  documentB.md
folder2/
  other-document.md

will be uploaded to Confluence like this:

document
folder1/
  documentA
  documentB
other-document

Dealing with empty folders

Passing --skip-empty will not create pages for empty folders.

Example ```text document.md folder1/ folder2/ folder3/ other-document.md folderA/ interesting-document.md folderB/ folderC/ lonely-document.md ```

will be uploaded as:

document
folder3/
  other-document
folderA/
  interesting-document
  folderC/
    lonely-document

Alternatively, you can specify --collapse-empty to merge empty folders together.

Example ```text document.md folder1/ folder2/ folder3/ other-document.md folderA/ interesting-document.md folderB/ folderC/ lonely-document.md ```

will be uploaded as:

document
folder1/folder2/folder3/
  other-document
folderA/
  interesting-document
  folderB/folderC/
    lonely-document

Terminal output format

By default, md2cf produces rich output with animated progress bars that are meant for human consumption. If the output is redirected to a file, the progress bars will not be displayed and only the final result will be written to the file. Error messages are always printed to standard error.

In addition to the default format, md2cf also supports two other output formats.

JSON output

When --output json is passed to md2cf, the JSON output for each page as returned by Confluence will be printed. Note that normal progress output will not be displayed.

⚠️ Please note that JSON entries will only be printed for page creation/updates. They will not be printed for attachment creation/updates and will not be printed for second-pass updates for relative links.

Minimal output

When passing the --output minimal option to md2cf, the tool will only print the final Confluence URL for each page, as in versions prior to 2.0.0. The normal progress output will be omitted.

⚠️ Note that URLs will only be printed for page creation/updates. They will not be printed for attachment creation/updates and will not be printed for second-pass updates for relative links.

Library usage

md2cf can of course be used as a Python library. It exposes two useful modules: the renderer and the API wrapper.

Renderer

Use the ConfluenceRenderer class to generate Confluence Storage Format output from a Markdown document.

import mistune
from md2cf.confluence_renderer import ConfluenceRenderer

markdown_text = "# Page title\n\nInteresting *content* here!"

renderer = ConfluenceRenderer(use_xhtml=True)
confluence_mistune = mistune.Markdown(renderer=renderer)
confluence_body = confluence_mistune(markdown_text)

API

md2cf embeds a teeny-tiny implementation of the Confluence Server REST API that allows you to create, read, and update pages.

from md2cf.api import MinimalConfluence

confluence = MinimalConfluence(host='https://example.com/rest/api', username='foo', password='bar')

confluence.create_page(space='TEST', title='Test page', body='<p>Nothing</p>', update_message='Created page')

page = confluence.get_page(title='Test page', space_key='TEST')
confluence.update_page(page=page, body='New content', update_message='Changed page contents')

md2cf's People

Contributors

bass-03 avatar bhrutledge avatar bjorns avatar dependabot[bot] avatar gabriel-t-harris avatar iamjackg avatar jannismain avatar jimstein3d avatar jmonsma avatar rtkjbillo avatar ssaraswati avatar sykmschmieder avatar timothybonci avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

md2cf's Issues

mermaid diagrams support

mermaid is a standard github way to embed diagrams

it would be amazing if e.g. confluence page were to render diagrams properly

I can see 2 options:

  1. using confluence mermaid plugin, but somehow making the page to pick it up
  2. rendering svg/png serverside and embedding them in a confluence page

do you have a particular view, in case someone would want to sponsor the work?

Request/feature: clean slate / delete subpages of parent before publish

If you change the header of a markdown file, I currently end up with two pages in confluence; one old dead page, and the new page maintained by the code.

I might have overlooked some of the many flags, but it would be great if I could set a flag that cleans the targeted parent's subpages, renames the existing confluence page (dif compare) or at least deletes those pages that are not represented in the code.

"With great power comes great responsibility" - I know, but I really need to have the code as my documentation source, and not have doubt about the content in confluence. So this would be a neat feature to me😄

If it is already there, by all means, correct me!

Feature Addition: "Autogenerated" Label Validation

Hey there,

In the process of using md2cf, I've implemented an additional feature that could be beneficial to the wider community.

Below is a summary of the new feature:

Autogenerated Label Validation: I've introduced a new flag --check-autogenerated-label which, when activated, checks for a 'marker' label on the Confluence pages.
This marker indicates if the page was autogenerated.
The name of the marker label can be provided via --autogenerated-flag-label.
If a page that's going to be updated doesn't have this label, the script will prompt the user and ask whether they wish to proceed with the update.

This feature aims to prevent accidental overwriting of manually created pages when mass updating pages via the script. I've developed an initial implementation to satisfy my immediate requirements, but I'm willing to develop a cleaner, more robust version if this feature is deemed useful for the project.

I would be delighted to hear your thoughts on this proposed enhancement.

Support "minorEdit" option

The confluence API supports the "minorEdit" option to not notify watchers when a page has been updated.

I would like to reduce the number of e-mails generated by automatically updating several pages at once, so I've implemented this for the update_page handler in #50 .

PyYaml 6.0 failing with cython 3 update

On or about July 14, cython 3 was released with breaking changes. pyyaml 6.0 didn't pin to 2.x, so it's now failing to install. This project pins to 6.0, so it's failing to install.

pyyaml 6.0.1 pins to cython <3, so it should work fine, but it hasn't been merged into master yet. I can open a PR to use pyyaml 6.0.1, but I'm not sure how you feel about using "unreleased" versions of dependencies.

Cloud `spaces` vs Server `space`

I did some hilarious scripting to clean up my markdown dump to make it acceptable to the tool, and now instead I'm getting

requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://<mycompany>.atlassian.net/wiki/space/TEST?expand=homepage

When I go to actually look at Confluence Cloud's URL for the TEST space it's https://<mycompany>.atlassian.net/wiki/spaces/TEST (note the plural spaces).

Have I missed something or found a bug? :)

authentication credentials are validated even with --dry-run in md2cf v2.1.0

with this command line "md2cf --host 'https://<my secret>.atlassian.net/wiki/rest/api' --username '<my secret>' --password '<my secret>' --space <my secret> --dry-run --no-gitignore 'C:\my-project\docs'" and with
"pip install md2cf==2.0.2" it will work.
But if I then do this "pip install md2cf==2.1.0" and run the command line again I now get this error:

Traceback (most recent call last):
File "", line 198, in run_module_as_main
File "", line 88, in run_code
File "C:\Python311\Scripts\md2cf.exe_main
.py", line 7, in
File "C:\Python311\Lib\site-packages\md2cf_main
.py", line 384, in main
space_info = confluence.get_space(
^^^^^^^^^^^^^^^^^^^^^
File "C:\Python311\Lib\site-packages\md2cf\api.py", line 259, in get_space
return self._get(f"content?spaceKey={space}/")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Python311\Lib\site-packages\md2cf\api.py", line 70, in _get
return self._request("GET", path, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Python311\Lib\site-packages\md2cf\api.py", line 66, in _request
r.raise_for_status()
File "C:\Python311\Lib\site-packages\requests\models.py", line 1021, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://<my secret>.atlassian.net/wiki/rest/api/space/<my secret>/?expand=homepage

NOTE: this url (https://.atlassian.net/wiki/rest/api/space/<my secret>/?expand=homepage) in the webbrowser print json content.

EDIT: the difference was that --dry-run is not respected in the new version. I still have problems with authentication credentials with the old version with --dry-run removed from command line.

Feature Request: Page Footers

The --preface-file is great, it's basically a header that gets set at the top of every file.

Can we also have a --postfix-file? Basically the same thing, but appending it as a footer to the page instead.

UnicodeDecodeError: 'gbk' codec can't decode byte

I have a document encoded by 'utf-8' but the program try to decode by 'gbk'

Traceback (most recent call last):
  File "C:\Users\vicat\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\vicat\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\vicat\AppData\Local\Programs\Python\Python38\Scripts\md2cf.exe\__main__.py", line 7, in <module>
  File "C:\Users\vicat\AppData\Local\Programs\Python\Python38\lib\site-packages\md2cf\__main__.py", line 350, in main
    md2cf.document.get_page_data_from_file_path(file_name)
  File "C:\Users\vicat\AppData\Local\Programs\Python\Python38\lib\site-packages\md2cf\document.py", line 140, in get_page_data_from_file_path
    markdown_lines = file_handle.readlines()
UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 2: illegal multibyte sequence

what should i do?

Argument `--force-unique` does not exist

When running this for a stack of our MD files we got an error stating:

Some documents have the same title. Update them or use --force-unique:

However, the --force-unique argument does not exist

1.3.0 available on Pypi but not here

Thank's a lot for your returns about #22.
I've tested your changes and it work fine !! :)
Don't know if it's a bug from me but I can't see your changes here in this repo (master).

Failed to upload image

I have an image notation in my markdown file

![The credential set form](images/auth-token-form.png)

I see this error when the file is uploaded as part of a folder with the new relative links flag enabled:

ERROR: Session.request() got an unexpected keyword argument 'format'    

I did some very brief digging and it looks like the parameters in api.py may not have been updated to match requests. I see on line 230:

                data={"comment": message} if message else None,
                format=(None, "json"),

and I'm thinking it should be something like:

                json={"comment": message} if message else None,

That's just a guess though.

KeyError: PosixPath('.') when uploading files

I get the following error when I try to upload to confluence:

$ md2cf --dry-run  --space xxx --password ${CONFLUENCE_PASSWORD} --username ${CONFLUENCE_USERNAME} --host ${CONFLUENCE_HOST} dist
Traceback (most recent call last):
  File "/home/xxx/.pyenv/versions/3.9.9/bin/md2cf", line 8, in <module>
    sys.exit(main())
  File "/home/xxx/.pyenv/versions/3.9.9/lib/python3.9/site-packages/md2cf/__main__.py", line 235, in main
    pages_to_upload += md2cf.document.get_pages_from_directory(
  File "/home/xxx/.pyenv/versions/3.9.9/lib/python3.9/site-packages/md2cf/document.py", line 85, in get_pages_from_directory
    folder_parent_title = folder_data[folder_parent_path]["title"]
KeyError: PosixPath('.')

adding "try-except" on line 85-116 will have me continue with this but is most likely not the solution.


            try:
                folder_parent_title = folder_data[folder_parent_path]["title"]

                if len(markdown_files) == 1 and collapse_single_pages:
                    parent_page_title = folder_parent_title
                else:
                    if collapse_empty:
                        folder_data[current_path]["title"] = current_path.relative_to(
                            folder_parent_path
                        )
                    if beautify_folders:
                        folder_data[current_path]["title"] = (
                            folder_data[current_path]["title"]
                            .replace("-", " ")
                            .replace("_", " ")
                            .capitalize()
                        )
                    elif use_pages_file and ".pages" in file_names:
                        with open(current_path.joinpath(".pages")) as pages_fp:
                            pages_file_contents = yaml.safe_load(pages_fp)
                        if "title" in pages_file_contents:
                            folder_data[current_path]["title"] = pages_file_contents[
                                "title"
                            ]
                    parent_page_title = folder_data[current_path]["title"]
                    processed_pages.append(
                        Page(
                            title=parent_page_title,
                            parent_title=folder_parent_title,
                            body="",
                        )
                    )
            except KeyError:
                print(f"invalld path, current_path: {current_path}")
                print(f"invalld path, type(current_path): {type(current_path)}")
                pass

output is:

invalld path, current_path: dist
invalld path, type(current_path): <class 'pathlib.PosixPath'>

Relative links break with a hash (#) to sub-section

Markdown reference to section from another (slask.md) file will not work after import to Confluence.
slask.md contains:

#  Main section

##  [sub-section](./child.md#sub-section)    
##  [sub-section](/child.md#sub-section)
##  [sub-section](child.md#sub-section)

This will result in broken links i confluence.

(/child.md#sub-section) results in a link like this https:///<my-secret>.atlassian.net/child.md#sub-section

Edit: out problem seem to be that urlparse.urlparse(link) marks sub-section as a fragment and then dont create a replacement_link.

Mistune 2 compatibility

Hi!

I noticed the program only works with Mistune 0.8.4. If the pytest suite is run with Mistune >= 2, the following is reported:

$ pytest
==================================================================== test session starts =====================================================================
platform linux -- Python 3.10.7, pytest-7.2.0, pluggy-1.0.0
rootdir: /tmp/md2cf, configfile: pytest.ini
plugins: pyfakefs-5.0.0, mock-1.11.1
collected 20 items / 5 errors                                                                                                                                

=========================================================================== ERRORS ===========================================================================
__________________________________________________ ERROR collecting tests/functional/test_full_rendering.py __________________________________________________
tests/functional/test_full_rendering.py:3: in <module>
    from md2cf import document
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:11: in <module>
    ???
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:52: in <module>
    ???
E   AttributeError: module 'mistune' has no attribute 'Renderer'
________________________________________________________ ERROR collecting tests/unit/test_document.py ________________________________________________________
tests/unit/test_document.py:1: in <module>
    import md2cf.document as doc
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:11: in <module>
    ???
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:52: in <module>
    ???
E   AttributeError: module 'mistune' has no attribute 'Renderer'
__________________________________________________________ ERROR collecting tests/unit/test_main.py __________________________________________________________
tests/unit/test_main.py:1: in <module>
    import md2cf.__main__ as main
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/__main__.py:15: in <module>
    ???
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:11: in <module>
    ???
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:52: in <module>
    ???
E   AttributeError: module 'mistune' has no attribute 'Renderer'
_________________________________________________________ ERROR collecting tests/unit/test_regex.py __________________________________________________________
tests/unit/test_regex.py:1: in <module>
    from md2cf.__main__ import CONTENT_HASH_REGEX
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/__main__.py:15: in <module>
    ???
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:11: in <module>
    ???
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:52: in <module>
    ???
E   AttributeError: module 'mistune' has no attribute 'Renderer'
________________________________________________________ ERROR collecting tests/unit/test_renderer.py ________________________________________________________
tests/unit/test_renderer.py:1: in <module>
    from md2cf.confluence_renderer import ConfluenceTag, ConfluenceRenderer
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:52: in <module>
    ???
E   AttributeError: module 'mistune' has no attribute 'Renderer'
================================================================== short test summary info ===================================================================
ERROR tests/functional/test_full_rendering.py - AttributeError: module 'mistune' has no attribute 'Renderer'
ERROR tests/unit/test_document.py - AttributeError: module 'mistune' has no attribute 'Renderer'
ERROR tests/unit/test_main.py - AttributeError: module 'mistune' has no attribute 'Renderer'
ERROR tests/unit/test_regex.py - AttributeError: module 'mistune' has no attribute 'Renderer'
ERROR tests/unit/test_renderer.py - AttributeError: module 'mistune' has no attribute 'Renderer'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 5 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
===================================================================== 5 errors in 0.34s ======================================================================

From what I could gather by searching the internet is the Renderer attribute having been replaced, however my "hack" in simply applying the following patch

--- a/md2cf/confluence_renderer.py      2022-11-09 16:26:31.031940156 +0100
+++ b/md2cf/confluence_renderer.py      2022-11-09 16:27:48.448477461 +0100
@@ -49,7 +49,7 @@
         self.children.append(child)

-class ConfluenceRenderer(mistune.Renderer):
+class ConfluenceRenderer(mistune.HTMLRenderer):
     def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
         super().__init__(**kwargs)
         self.strip_header = strip_header

yields various other test failures:

$ pytest
==================================================================== test session starts =====================================================================
platform linux -- Python 3.10.7, pytest-7.2.0, pluggy-1.0.0
rootdir: /tmp/md2cf, configfile: pytest.ini
plugins: pyfakefs-5.0.0, mock-1.11.1
collected 61 items                                                                                                                                           

tests/functional/test_full_rendering.py F                                                                                                              [  1%]
tests/unit/test_confluence.py .................                                                                                                        [ 29%]
tests/unit/test_document.py .FFFFFFFF.....                                                                                                             [ 52%]
tests/unit/test_ignored_files.py ...                                                                                                                   [ 57%]
tests/unit/test_main.py .                                                                                                                              [ 59%]
tests/unit/test_regex.py ...                                                                                                                           [ 63%]
tests/unit/test_renderer.py .........F..F.FFF.....                                                                                                     [100%]

========================================================================== FAILURES ==========================================================================
_____________________________________________________________________ test_full_document _____________________________________________________________________

script_loc = local('/tmp/md2cf/tests/functional')

    def test_full_document(script_loc):
        markdown_path = script_loc.join("test.md")
    
        with open(str(script_loc.join("result.xml"))) as result_file:
            result_data = result_file.read()
    
>       page = document.get_page_data_from_file_path(markdown_path)

tests/functional/test_full_rendering.py:22: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c790ebf0>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
_______________________________________________________________ test_get_pages_from_directory ________________________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c7d7b7c0>

    def test_get_pages_from_directory(fs):
        fs.create_file("/rootfolder/rootfolderfile.md")
        fs.create_dir("/rootfolder/emptydir")
        fs.create_file("/rootfolder/parent/child/childfile.md")
    
>       result = doc.get_pages_from_directory(Path("/rootfolder"))

/tmp/md2cf/tests/unit/test_document.py:18: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c6d041f0>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
____________________________________________________ test_get_pages_from_directory_collapse_single_pages _____________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c6df3340>

    def test_get_pages_from_directory_collapse_single_pages(fs):
        fs.create_file("/rootfolder/rootfolderfile.md")
        fs.create_file("/rootfolder/parent/child/childfile.md")
    
>       result = doc.get_pages_from_directory(
            Path("/rootfolder"), collapse_single_pages=True
        )

/tmp/md2cf/tests/unit/test_document.py:38: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c6df1420>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
__________________________________________ test_get_pages_from_directory_collapse_single_pages_no_non_empty_parent ___________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c6d06260>

    def test_get_pages_from_directory_collapse_single_pages_no_non_empty_parent(fs):
        fs.create_file("/rootfolder/parent/child/childfile.md")
    
>       result = doc.get_pages_from_directory(
            Path("/rootfolder"), collapse_single_pages=True
        )

/tmp/md2cf/tests/unit/test_document.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c7dd89d0>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
__________________________________________________________ test_get_pages_from_directory_skip_empty __________________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c6dd2410>

    def test_get_pages_from_directory_skip_empty(fs):
        fs.create_file("/rootfolder/rootfolderfile.md")
        fs.create_file("/rootfolder/parent/child/childfile.md")
    
>       result = doc.get_pages_from_directory(Path("/rootfolder"), skip_empty=True)

/tmp/md2cf/tests/unit/test_document.py:77: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c6dd0280>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
________________________________________________ test_get_pages_from_directory_skip_empty_no_non_empty_parent ________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c7dda260>

    def test_get_pages_from_directory_skip_empty_no_non_empty_parent(fs):
        fs.create_file("/rootfolder/parent/child/childfile.md")
    
>       result = doc.get_pages_from_directory(Path("/rootfolder"), skip_empty=True)

/tmp/md2cf/tests/unit/test_document.py:99: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c7ddacb0>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
________________________________________________________ test_get_pages_from_directory_collapse_empty ________________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c6d05450>

    def test_get_pages_from_directory_collapse_empty(fs):
        fs.create_file("/rootfolder/rootfolderfile.md")
        fs.create_file("/rootfolder/parent/child/childfile.md")
    
>       result = doc.get_pages_from_directory(Path("/rootfolder"), collapse_empty=True)

/tmp/md2cf/tests/unit/test_document.py:118: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c790c9a0>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
______________________________________________ test_get_pages_from_directory_collapse_empty_no_non_empty_parent ______________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c79305e0>

    def test_get_pages_from_directory_collapse_empty_no_non_empty_parent(fs):
        fs.create_file("/rootfolder/parent/child/childfile.md")
    
>       result = doc.get_pages_from_directory(Path("/rootfolder"), collapse_empty=True)

/tmp/md2cf/tests/unit/test_document.py:140: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c7932230>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
_______________________________________________________ test_get_pages_from_directory_beautify_folders _______________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f99c790df30>

    def test_get_pages_from_directory_beautify_folders(fs):
        fs.create_file("/rootfolder/ugly-folder/another_yucky_folder/childfile.md")
    
>       result = doc.get_pages_from_directory(Path("/rootfolder"), beautify_folders=True)

/tmp/md2cf/tests/unit/test_document.py:158: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:165: in get_pages_from_directory
    processed_page = get_page_data_from_file_path(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:191: in get_page_data_from_file_path
    page = get_page_data_from_lines(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:214: in get_page_data_from_lines
    page = parse_page(
/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/document.py:238: in parse_page
    renderer = ConfluenceRenderer(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c6df0400>, strip_header = False, remove_text_newlines = False
kwargs = {'use_xhtml': True}

    def __init__(self, strip_header=False, remove_text_newlines=False, **kwargs):
>       super().__init__(**kwargs)
E       TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:54: TypeError
-------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------
No git root found, gitignore checking disabled.
____________________________________________________________________ test_renderer_reinit ____________________________________________________________________

    def test_renderer_reinit():
        renderer = ConfluenceRenderer()
>       renderer.header("this is a title", 1)

tests/unit/test_renderer.py:102: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c790cca0>, text = 'this is a title', level = 1, raw = None

    def header(self, text, level, raw=None):
        if self.title is None and level == 1:
            self.title = text
            # Don't duplicate page title as a header
            if self.strip_header:
                return ""
    
>       return super(ConfluenceRenderer, self).header(text, level, raw=raw)
E       AttributeError: 'super' object has no attribute 'header'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:71: AttributeError
______________________________________________________________ test_renderer_header_sets_title _______________________________________________________________

    def test_renderer_header_sets_title():
        test_header = "this is a header"
        renderer = ConfluenceRenderer()
    
>       renderer.header(test_header, 1)

tests/unit/test_renderer.py:141: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c6df03a0>, text = 'this is a header', level = 1, raw = None

    def header(self, text, level, raw=None):
        if self.title is None and level == 1:
            self.title = text
            # Don't duplicate page title as a header
            if self.strip_header:
                return ""
    
>       return super(ConfluenceRenderer, self).header(text, level, raw=raw)
E       AttributeError: 'super' object has no attribute 'header'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:71: AttributeError
____________________________________________________ test_renderer_header_lower_level_does_not_set_title _____________________________________________________

    def test_renderer_header_lower_level_does_not_set_title():
        test_header = "this is a header"
        renderer = ConfluenceRenderer()
    
>       renderer.header(test_header, 2)

tests/unit/test_renderer.py:159: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c6901ea0>, text = 'this is a header', level = 2, raw = None

    def header(self, text, level, raw=None):
        if self.title is None and level == 1:
            self.title = text
            # Don't duplicate page title as a header
            if self.strip_header:
                return ""
    
>       return super(ConfluenceRenderer, self).header(text, level, raw=raw)
E       AttributeError: 'super' object has no attribute 'header'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:71: AttributeError
________________________________________________________ test_renderer_header_later_level_sets_title _________________________________________________________

    def test_renderer_header_later_level_sets_title():
        test_lower_header = "this is a lower header"
        test_header = "this is a header"
        renderer = ConfluenceRenderer()
    
>       renderer.header(test_lower_header, 2)

tests/unit/test_renderer.py:169: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c6dd3dc0>, text = 'this is a lower header', level = 2, raw = None

    def header(self, text, level, raw=None):
        if self.title is None and level == 1:
            self.title = text
            # Don't duplicate page title as a header
            if self.strip_header:
                return ""
    
>       return super(ConfluenceRenderer, self).header(text, level, raw=raw)
E       AttributeError: 'super' object has no attribute 'header'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:71: AttributeError
_________________________________________________________ test_renderer_header_only_sets_first_title _________________________________________________________

    def test_renderer_header_only_sets_first_title():
        test_header = "this is a header"
        test_second_header = "this is another header"
        renderer = ConfluenceRenderer()
    
>       renderer.header(test_header, 1)

tests/unit/test_renderer.py:180: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <md2cf.confluence_renderer.ConfluenceRenderer object at 0x7f99c7dc8100>, text = 'this is a header', level = 1, raw = None

    def header(self, text, level, raw=None):
        if self.title is None and level == 1:
            self.title = text
            # Don't duplicate page title as a header
            if self.strip_header:
                return ""
    
>       return super(ConfluenceRenderer, self).header(text, level, raw=raw)
E       AttributeError: 'super' object has no attribute 'header'

/usr/local/lib/python3.10/site-packages/md2cf-1.5.1-py3.10.egg/md2cf/confluence_renderer.py:71: AttributeError
================================================================== short test summary info ===================================================================
FAILED tests/functional/test_full_rendering.py::test_full_document - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory_collapse_single_pages - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory_collapse_single_pages_no_non_empty_parent - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory_skip_empty - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory_skip_empty_no_non_empty_parent - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory_collapse_empty - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory_collapse_empty_no_non_empty_parent - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_document.py::test_get_pages_from_directory_beautify_folders - TypeError: HTMLRenderer.__init__() got an unexpected keyword argument 'use_xhtml'
FAILED tests/unit/test_renderer.py::test_renderer_reinit - AttributeError: 'super' object has no attribute 'header'
FAILED tests/unit/test_renderer.py::test_renderer_header_sets_title - AttributeError: 'super' object has no attribute 'header'
FAILED tests/unit/test_renderer.py::test_renderer_header_lower_level_does_not_set_title - AttributeError: 'super' object has no attribute 'header'
FAILED tests/unit/test_renderer.py::test_renderer_header_later_level_sets_title - AttributeError: 'super' object has no attribute 'header'
FAILED tests/unit/test_renderer.py::test_renderer_header_only_sets_first_title - AttributeError: 'super' object has no attribute 'header'
=============================================================== 14 failed, 47 passed in 0.51s ================================================================

I tested the above with Mistune 2.0.4 on Python 3.8 and Python 3.10.

Unfortunately this exceeds my Python skillset - but maybe support for Mistune 2 could be considered for a future release?

Best,
Georg

Convert URL-encoded attachment paths?

Hey there, I checked for previous issues about this, but didn't find anything that seemed relevant.
I'm working with a dump of markdown from a popular wiki-like tool that starts with an N, and for attached images, it exports like this:

Export-folder:
 John a10958b569844bf9bc44097757e559f8.md
 |_ John a10958b569844bf9bc44097757e559f8
     |_profile-picture.jpeg

But in the export file, it's referencing URL-encoded paths, so the spaces are %20, emojis are written out, etc.

![profile-picture.jpeg](John%20a10958b569844bf9bc44097757e559f8/profile-picture.jpeg)

The result is that the tool throws

❌ ERROR: attachment (path) for page (page name) does not exist 

Am I doing something wrong here, or do I just need to pump these through some script to convert the file names and paths to URL-encoded ones? At this point I'm not sure if this is a bug or a feature request. :)

Remove top-level header when used as page title

From my initial testing, it looks like a when the page title is set using "the first top-level header found in the document (i.e. the first # header)", the title appears twice in Confluence: once at the top of the page, and again in the body. In that scenario, could it be removed during the rendering process?

Maybe here?

def header(self, text, level, raw=None):
if self.title is None and level == 1:
self.title = text
return super(ConfluenceRenderer, self).header(text, level, raw=raw)

folder upload breaks when --no-gitignore is omitted

I have a MkDocs project with documentation available, for this I have a .gitignore file for python.

However when I target a folder the action fails when I omit the --no-gitignore flag.

Run md2cf --host 'https://petermefrandsen.atlassian.net/wiki/rest/api' --username *** -*** --space 'md2cf' ./docs
GitHub action job

✔️ Run md2cf --host 'https://petermefrandsen.atlassian.net/wiki/rest/api' --username *** -*** --space 'md2cf' --no-gitignore ./docs
GitHub action job

Run md2cf --host 'https://petermefrandsen.atlassian.net/wiki/rest/api' --username *** -*** --space 'md[2](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:2)cf' ./docs
  md2cf --host 'https://petermefrandsen.atlassian.net/wiki/rest/api' --username *** -*** --space 'md2cf' ./docs
  shell: /usr/bin/bash -e {0}
  env:
    pythonLocation: /opt/hostedtoolcache/Python/[3](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:3).11.1/x6[4](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:4)
    LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.11.1/x64/lib
Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.11.1/x64/bin/md2cf", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/md2cf/__main__.py", line 403, in main
    pages_to_upload = collect_pages_to_upload(args)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/md2cf/__main__.py", line [5](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:5)15, in collect_pages_to_upload
    pages_to_upload += md2cf.document.get_pages_from_directory(
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x[6](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:6)4/lib/python3.11/site-packages/md2cf/document.py", line 109, in get_pages_from_directory
    markdown_files = [
                     ^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/md2cf/document.py", line 110, in <listcomp>
    path for path in markdown_files if not git_repo.is_ignored(path)
                                           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/md2cf/ignored_files.py", line 85, in is_ignored
    return any([m(filepath) for m in matchers])
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/md2cf/ignored_files.py", line 85, in <listcomp>
    return any([m(filepath) for m in matchers])
                ^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/gitignore_parser.py", line 32, in <lambda>
    return lambda file_path: any(r.match(file_path) for r in rules)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/gitignore_parser.py", line 32, in <genexpr>
    return lambda file_path: any(r.match(file_path) for r in rules)
                                 ^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/gitignore_parser.py", line 143, in match
    if re.search(self.regex, rel_path):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/re/__init__.py", line 1[7](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:8)6, in search
    return _compile(pattern, flags).search(string)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/re/__init__.py", line 294, in _compile
    p = _compiler.compile(pattern, flags)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/re/_compiler.py", line 743, in compile
    p = _parser.parse(p, flags)
        ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/re/_parser.py", line 9[8](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:9)0, in parse
    p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.[11](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:12).1/x64/lib/python3.11/re/_parser.py", line 455, in _parse_sub
    itemsappend(_parse(source, state, verbose, nested + 1,
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/re/_parser.py", line 8[41](https://github.com/petermefrandsen/mkdocs-deployment/actions/runs/4043527881/jobs/6952549020#step:5:42), in _parse
    raise source.error('global flags not at the start '
re.error: global flags not at the start of the expression at position 1
Error: Process completed with exit code 1.

Title Checking is Case Sensitive but Confluence Page Uniqueness is Insensitive

Got a good error when running this tool on my repo for "identical titles".

These are the documents (and path, if available) with identical titles:

BUT....

If the title and the path are different based on case, it makes it through the smoke test, and fails in upload.

Screenshot 2023-08-14 104355

b'{"statusCode":400,"data":{"authorized":true,"valid":true,"errors":[],"successful":true},"message":"com.atlassian.confluence.api.service.exceptions.BadRequestException: A page with this title already
exists: A page already exists with the same TITLE in this space"}'   

Images displayed with a width of less than 400px

Hi,

we have a markdown that contains the following piece of code:

![title](my-image.drawio.svg)

It gets correctly uploaded, but it's displayed with a very narrow width (less than 400px). Same happens if we use a png file (size is almost 900px).

Is there a way to instruct Confluence to use a greater width?

We are using version 2.3.0 of md2cf.

Error when markdown missing top level header

The markdown requires a top level header # Header to create the page title. This should not be required to be considered valid markdown.

This line assumes h1 exists.
page_title = soup.h1.text or file_name

Here is the resulting error.

Traceback (most recent call last):
  File "/home/charles/.local/bin/md2cf", line 11, in <module>
    sys.exit(main())
  File "/home/charles/.local/lib/python3.6/site-packages/md2cf/__main__.py", line 91, in main
    page_data = page_data_from_file_name(file_name)
  File "/home/charles/.local/lib/python3.6/site-packages/md2cf/__main__.py", line 49, in page_data_from_file_name
    page_title = soup.h1.text or file_name
AttributeError: 'NoneType' object has no attribute 'text'

403 client error "allowedInReadOnlyMode:true" prevents update to page

I have a space with multiple pages. I pushed an update to one of the pages then tried another page in the same space and get this error:

Updating page: pageupdate.md
403 Client Error:  for url: https://confluence.<example>.com/rest/api/content/<pageid> - b'{"statusCode":403,"data":{"authorized":false,"valid":true,"allowedInReadOnlyMode":true,"errors":[],"successful":false},"message":"Could not update Content of type : class com.atlassian.confluence.pages.Page with id <pageid>","reason":"Forbidden"}'

Any tips on this? I've verified that I have read/write permissions on the page.

Error parsing xhtml: Unexpected character \'>\' (code 62) expected \'=\'\\n

I have a markdown file and I convert it into a mediawiki format using pypandoc and then use md2cf to upload to confluence. However, I'm facing the issue ( pasted in subject ) everytime i try to run the code.

Can you help me in pointing out what could be happening?

Unfortunately, the file that i'm trying to convert is a proprietary document and i can't share it here.

Uploading attachments broken in 1.0.4

I get the following error when I try to create a page using md2cf v1.0.4:

admrfinch:devops:tmp$md2cf -s '~rfinch' DEMO.md
Creating new page: Bug Demo
Uploading attachments for page: Bug Demo
Uploading file: charizard-pokemon.png
405 Client Error:  for url: https://confluence.idicore.com/rest/api/content/358259237/child/attachment?allowDuplicated=true - b''

after downgrading to 1.0.3:

$ md2cf -s '~rfinch' DEMO.md
Updating page: Bug Demo
Uploading attachments for page: Bug Demo
Uploading file: charizard-pokemon.png
$ cat DEMO.md
# Bug Demo

![](charizard-pokemon.png)

parent_title and rate limits

Hi!
I really like md2cf! Awesome! I created a small simple Github Action for syncing Github md-files to Confluence in my company, and it works really well. The use of it started spreading to other teams, and some issues surfaced :-)

  1. it says you can use either parent_id or parent_title, but I can't get parent_title to work. it says Unexpected input(s) 'parent_title', valid inputs are ['entryPoint', 'args', 'github_f', 'space', 'confluence_token', 'confluence_host', 'parent_id'] and I don't think it's due to something I did... but maybe I'm mistaken. I looked in the api.py and I can't find any reference to parent_title.

  2. A team that has many md-files to sync ran into rate limiting issues when trying to sync them. It is due to the Confluence rate limiting settings in our company off course (and we are trying to increase that limit), but I did some googling and came across this: https://confluence.atlassian.com/doc/adjusting-your-code-for-rate-limiting-992679008.html
    Any thought on adding something like this in your code for the "micro-implementation" of the ConfluenceServer REST API?

I can provide source code if needed. Otherwise just happy for some thoughts on above issues :-)
Thanks
/Pixie

Feature Request: Add labels to published pages

Extend yaml frontmatter parsing to read labels key:

---
title: Page Title
labels: [label1, label2]
---
...

Apply those labels to confluence page after creation. When updating pages, I would probably delete existing labels from the page and apply those listed in yaml frontmatter, to ensure they are in sync with the labels listed in the markdown file.

certificate verify failed: self signed certificate in certificate chain

Possible solve Ref

md2cf --host 'https://confluence.********.com/rest/api' --username ************ --password '************' --space TEST document.md
ERROR: HTTPSConnectionPool(host='confluence.kordia.net', port=443): Max retries exceeded with url: /rest/api/content?title=md2cf+test&spaceKey=TEST (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1129)')))

Link parsing fails for document references

Hi,

I just noticed that it's not possible to use md2cf anymore with document references like table of contents, since the relative path detector detects the paths that start with #

[Frequently Asked Questions](#frequently-asked-questions-osep)

Having something like this in the document results in

Page test.md has a relative link to , which is not in the list of pages to be uploaded.  

Skip Hidden Folders

When uploading whole directories, like whole repos, markdown files found in hidden folders are also published.

This causes the common practice of PR Templates in the .github or the MR Templates under .gitlab to be published along with the other repo items. This is not desired when you have your markdown files scattered across your repo.

updating attachments not allowed

Currently the code for uploading attachments calls the Create Attachment method (https://developer.atlassian.com/cloud/confluence/rest/api-group-content---attachments/#api-wiki-rest-api-content-id-child-attachment-post), which does not allow updates, only the initial upload.

If instead we used the Create or Update Attachment method (https://developer.atlassian.com/cloud/confluence/rest/api-group-content---attachments/#api-wiki-rest-api-content-id-child-attachment-put), this should allow the same call to upsert the attachments instead.

Handle relative-links within documents

Hello,

I just looked at your project and it does seem to be super-promising for the use-case I have. One of the features that I am missing it the ability to handle relative links within the Markdown files. In the past I tried another tools and provided a pull-request for that functionality, but there have been a few issues with the implementation. Please have a look here for reference:

justmiles/go-markdown2confluence#40

Doing this in Python does sound way more straight forward and sustainable and I was wondering if you had already something in store or maybe an idea on how this could be implemented.

Feature-wise it would be something like

  • Identify relative paths within the document
  • Replace the paths with the Confluence pages
  • Create the Confluence page if it does not already exist

I'd be happy to help out!

Best,
Matthias

Upgrading Mistune

The version of Mistune currently used by md2cf is very old.

An example of a problem this causes, is that the version in use creates invalid XHTML if it encounters a double quote inside some image alt text.

I've been working on an upgrade, there various changes to make, feedback welcome, this is my first contribution here :)

#81

Feature request: only change when the content changes when uploading (folders)

Hi there,

Background

I recently scripted a mirror from the documentation of one of our projects to our confluence server. Every time something gets merged onto the default branch md2cf gets called and the documentation in confluence gets updated.

Problem

However, it seems that every page gets touched, even if the content didn't change.
With the email subscriptions of these pages my colleagues now get unnecessary notifications about seemingly changed content.

Possible solution

If the generated content would be compared with the content that is currently live on the server, and only uploaded if these two differ, the problem would be solved.

I'll try to implement it. Of course a hint where to start would be helpful 😉

Edit: only had time for the prototype below

Question/Feature Request: Inject custom Confluence Macros within Markdown

I have a Confluence environment with a handful of custom macros that are really useful - custom formatting, integrations, etc.

I'd like to reference these within the Markdown that I'm passing to md2cf. By default it just treats it as HTML and escapes it (to be expected).

Is there a way currently (or if not, feature request!) to be able to provide a macro's XML within my Markdown files and have md2cf leave it as-is when it generates the payload it sends over to Confluence?

Unicode

I don't know if it's a md2cf or my problem. I have several md's with umlauts (äöü etc.). These are not converted correctly.
Can I fix this somewhere?
I am unfortunately not a gifted Python programmer

AttributeError: module 'mistune' has no attribute 'Renderer'

Traceback (most recent call last):
  File "/Users/eeshan.jaiswal/.pyenv/versions/3.9.0/envs/gitlab_script/bin/md2cf", line 5, in <module>
    from md2cf.__main__ import main
  File "/Users/eeshan.jaiswal/.pyenv/versions/3.9.0/envs/gitlab_script/lib/python3.9/site-packages/md2cf/__main__.py", line 13, in <module>
    import md2cf.document
  File "/Users/eeshan.jaiswal/.pyenv/versions/3.9.0/envs/gitlab_script/lib/python3.9/site-packages/md2cf/document.py", line 9, in <module>
    from md2cf.confluence_renderer import ConfluenceRenderer
  File "/Users/eeshan.jaiswal/.pyenv/versions/3.9.0/envs/gitlab_script/lib/python3.9/site-packages/md2cf/confluence_renderer.py", line 52, in <module>
    class ConfluenceRenderer(mistune.Renderer):
AttributeError: module 'mistune' has no attribute 'Renderer'

Using Python 3.9.0

ERROR: list index out of range

During deployment of our large repository we got a list out of index error.

Command:

                md2cf --host $(confluence-host) --token $(confluence-api-key) \
                  ./documentation/docs/ \
                  --space $(confluence-space) \
                  --parent-title "Platform" \
                  --insecure \
                  --only-changed \
                  --strip-top-header

Output:

2023-02-14T19:53:07.7272110Z  Total progress ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00           
2023-02-14T19:53:09.0155527Z                                                                                                                                                                 
2023-02-14T19:53:09.0158112Z  📄️ Platform          ━━━━━━━━━━━━━  ❌ Error while uploading 
2023-02-14T19:53:09.0159179Z  📂️ documentation                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0160068Z  ├── 📂️ 05-dashboards                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0160907Z  │   └── 📂️ 01-grafana                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0162100Z  │       └── 📄️ Grafana                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0162952Z  ├── 📂️ 08-logs                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0163763Z  │   ├── 📂️ 03-loki                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0165744Z  │   │   └── 📄️ Loki                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0167407Z  │   │       ├── 📎️ logs-loki-layout.png━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0168252Z  │   │       └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0169094Z  │   │           logs-loki-configuratio…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0169944Z  │   ├── 📂️ 02-fluentd                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0170767Z  │   │   └── 📄️ FluentD                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0171567Z  │   │       └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0172730Z  │   │           logs-fluentd-layout.png━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0178884Z  │   └── 📂️ 01-fluentbit                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0180068Z  │       └── 📄️ FluentBit               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0180898Z  │           ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0181732Z  │           │   logs-fluentbit-configu…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0182695Z  │           ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0183536Z  │           │   logs-fluentbit-layout.…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0184338Z  │           └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0185158Z  │               logs-fluentbit-design.…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0186054Z  ├── 📂️ 10-network                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0186872Z  │   ├── 📄️ Network                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0187714Z  │   ├── 📂️ 03-nginx-ingress-controller ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0188581Z  │   │   └── 📄️ Nginx Ingress Controller━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0189387Z  │   │       └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0190325Z  │   │           network-network-mappin…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0191176Z  │   └── 📂️ 02-network-mapping          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0192021Z  │       └── 📄️ Network Mapping         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0193046Z  ├── 📂️ 09-metrics                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0194124Z  │   ├── 📂️ 01-alertmanager             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0194964Z  │   │   └── 📄️ AlertManager            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0195767Z  │   │       └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0196641Z  │   │           metrics-alertmanager.p…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0197422Z  │   ├── 📂️ 02-prometheus               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0198183Z  │   │   └── 📄️ Prometheus              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0198925Z  │   │       └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0199659Z  │   │           metrics-prometheus-lay…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0200454Z  │   ├── 📂️ 04-victoria-metrics-alert   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0201264Z  │   │   └── 📄️ Victoria Metrics Alert  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0202401Z  │   │       └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0203212Z  │   │           metrics-victoria-metri…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0205464Z  │   └── 📂️ 03-victoria-metrics         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0206361Z  │       └── 📄️ Victoria Metrics        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0207171Z  │           ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0207979Z  │           │   metrics-victoria-metri…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0208919Z  │           └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0209737Z  │               metrics-victoria-metri…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0210574Z  ├── 📂️ 06-gitops                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0211599Z  │   └── 📂️ 01-argocd                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0212412Z  │       └── 📄️ ArgoCD                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0213233Z  │           └── 📎️ argocd-1.png        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0214126Z  ├── 📂️ 01-proposition   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0215024Z  │   └── 📄️ PROPOSITION  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0215992Z  │       └── 📎️                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0216848Z  │           ./service-propositi…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0217673Z  ├── 📂️ 03-infrastructure               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0218503Z  │   └── 📄️ Infrastructure              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0219543Z  ├── 📂️ 02-generic-design               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0220371Z  │   └── 📄️ Generic Design              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0221210Z  │       ├── 📎️ ./generic-design-1.png  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0222060Z  │       ├── 📎️ ./generic-design-2.png  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0223607Z  │       ├── 📎️ ./generic-design-3.png  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0224473Z  │       ├── 📎️ ./generic-design-4.png  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0225558Z  │       ├── 📎️ ./generic-design-5.png  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0229346Z  │       └── 📎️ ./generic-design-6.png  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0230889Z  ├── 📂️ 04-procedures                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0232049Z  │   ├── 📂️ 03-operational-guide        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0232934Z  │   │   ├── 📄️ Onboarding ArgoCD       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0233824Z  │   │   ├── 📄️ Onboarding new Client   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0234675Z  │   │   │   Cluster                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0235476Z  │   │   │   └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0236293Z  │   │   │       ./operational-guide-on…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0237134Z  │   │   ├── 📄️ Onboarding External     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0237963Z  │   │   │   Secret Operator (ESO)      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0238763Z  │   │   │   └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0239585Z  │   │   │       ./operational-guide-on…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0240579Z  │   │   ├── 📄️ Onboarding logs         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0241384Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0242193Z  │   │   │   │   operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0243004Z  │   │   │   └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0262792Z  │   │   │       ./operational-guide-on…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0264232Z  │   │   ├── 📄️ Onboarding dashboards   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0265135Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0265981Z  │   │   │   │   operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0266805Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0267898Z  │   │   │   │   operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0270239Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0271075Z  │   │   │   │   operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0271892Z  │   │   │   └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0272705Z  │   │   │       operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0273563Z  │   │   ├── 📄️ Onboarding New Alert    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0274379Z  │   │   │   Channel                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0276513Z  │   │   │   └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0277615Z  │   │   │       operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0278540Z  │   │   ├── 📄️ Onboarding App Metrics  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0279364Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0280180Z  │   │   │   │   operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0280993Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0281809Z  │   │   │   │   operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0282606Z  │   │   │   └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0283743Z  │   │   │       operational-guide-onbo…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0284633Z  │   │   └── 📄️ Onboarding an Alert     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0285445Z  │   │       └── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0286288Z  │   │           ./operational-guide-on…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0287361Z  │   ├── 📂️ 02-customer-guide           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0288193Z  │   │   ├── 📄️ Onboarding a Cluster in ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0289008Z  │   │   │   ArgoCD                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0289821Z  │   │   ├── 📄️ Onboarding New          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0290628Z  │   │   │   Application                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0291421Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0292226Z  │   │   │   │   customer-guide-introdu…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0293023Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0293987Z  │   │   │   │   customer-guide-onboard…━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0294927Z  │   │   │   ├── 📎️                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⠦   
2023-02-14T19:53:09.0295965Z  │   │   │   │   customer-guide-onboard…                                        
2023-02-14T19:53:09.0296822Z  │   │   │   ├── 📎️                                                             
2023-02-14T19:53:09.0297603Z  │   │   │   │   customer-guide-onboard…                                        
2023-02-14T19:53:09.0298366Z  │   │   │   ├── 📎️                                                             
2023-02-14T19:53:09.0299138Z  │   │   │   │   customer-guide-onboard…                                        
2023-02-14T19:53:09.0299914Z  │   │   │   └── 📎️                                                             
2023-02-14T19:53:09.0302040Z  │   │   │       customer-guide-onboard…                                        
2023-02-14T19:53:09.0303004Z  │   │   └── 📄️ Onboarding New Grafana                                          
2023-02-14T19:53:09.0304046Z  │   │       Datasource                                                         
2023-02-14T19:53:09.0304827Z  │   ├── 📂️ 01-getting-started                                                  
2023-02-14T19:53:09.0306613Z  │   │   ├── 📄️ Checklist template                                              
2023-02-14T19:53:09.0307581Z  │   │   │   pre-requirements                                                   
2023-02-14T19:53:09.0308370Z  │   │   ├── 📄️ Structure                                                       
2023-02-14T19:53:09.0309156Z  │   │   ├── 📄️ Generic design                                                  
2023-02-14T19:53:09.0309934Z  │   │   └── 📄️ Around                                                      
2023-02-14T19:53:09.0310709Z  │   └── 📂️ 04-developer-guide                                                  
2023-02-14T19:53:09.0311507Z  │       ├── 📄️ GitOps Structure                                                
2023-02-14T19:53:09.0312300Z  │       │   ├── 📎️                                                             
2023-02-14T19:53:09.0313071Z  │       │   │   developer-guide-introd…                                        
2023-02-14T19:53:09.0313845Z  │       │   ├── 📎️                                                             
2023-02-14T19:53:09.0314609Z  │       │   │   developer-guide-gitops…                                        
2023-02-14T19:53:09.0315361Z  │       │   └── 📎️                                                             
2023-02-14T19:53:09.0316131Z  │       │       developer-guide-gitops…                                        
2023-02-14T19:53:09.0316919Z  │       └── 📄️ Git strategy                                                    
2023-02-14T19:53:09.0317676Z  │           ├── 📎️                                                             
2023-02-14T19:53:09.0318445Z  │           │   developer-guide-git-fl…                                        
2023-02-14T19:53:09.0319627Z  │           ├── 📎️                                                             
2023-02-14T19:53:09.0320348Z  │           │   developer-guide-git-fl…                                        
2023-02-14T19:53:09.0321067Z  │           ├── 📎️                                                             
2023-02-14T19:53:09.0321954Z  │           │   developer-guide-git-fl…                                        
2023-02-14T19:53:09.0322722Z  │           └── 📎️                                                             
2023-02-14T19:53:09.0323494Z  │               developer-guide-git-fl…                                        
2023-02-14T19:53:09.0324290Z  └── 📂️ 07-integrations                                                         
2023-02-14T19:53:09.0325066Z      ├── 📂️ 04-hungryhippo                                                      
2023-02-14T19:53:09.0325855Z      │   └── 📄️ HungryHippo                                                     
2023-02-14T19:53:09.0326632Z      │       └── 📎️                                                             
2023-02-14T19:53:09.0327544Z      │           integrations-hungryhip…                                        
2023-02-14T19:53:09.0346026Z      ├── 📂️ 01-aad-pod-identity                                                 
2023-02-14T19:53:09.0346934Z      │   └── 📄️ AAD Pod Identity                                                
2023-02-14T19:53:09.0347842Z      │       └── 📎️                                                             
2023-02-14T19:53:09.0348642Z      │           integrations-aad-pod-i…                                        
2023-02-14T19:53:09.0349490Z      ├── 📂️ 02-external-secret-operator                                         
2023-02-14T19:53:09.0351842Z      │   └── 📄️ External Secret Operator                                        
2023-02-14T19:53:09.0352683Z      │       └── 📎️                                                             
2023-02-14T19:53:09.0353464Z      │           integrations-external-…                                        
2023-02-14T19:53:09.0354656Z      └── 📂️ 03-grafana-integration                                              
2023-02-14T19:53:09.0355490Z          └── 📄️ Grafana Integration                                             
2023-02-14T19:53:09.0356201Z              (Grafana Sync)                                                     
2023-02-14T19:53:09.0356954Z              └── 📎️                                                             
2023-02-14T19:53:09.0357732Z                  integrations-grafana-s…                                        
2023-02-14T19:53:09.0358279Z                                                                                 
2023-02-14T19:53:09.0359067Z  Total progress                                            1% -:--:--           
2023-02-14T19:53:09.0359767Z                                                                                 ERROR: list index out of range 
2023-02-14T19:53:09.0578898Z ##[error]Bash exited with code '1'.

`<kbd>` does not render in Confluence

Hi there,

I'm sorry to bother you again with a feature request but I really like md2cf and want to make the most of it without using some kind of static site generator.

Today I saw that the <kbd></kbd> syntax renders as regular text in Confluence. This is probably intended behavior, as not every Confluence instance can interpret raw HTML (or at least the one I am working with doesn't).

However, this is the recommended way to present keyboard shortcuts in Markdown, supported by both GFM and GLFM.

Example

 <kbd>ctrl</kbd>+<kbd>alt</kbd>+<kbd>p</kbd> / <kbd>⌘</kbd> <kbd>⌥</kbd> <kbd>p</kbd>

is currently rendered like this:

image

when something like this would be nicer:
CleanShot 2022-09-07 at 13 24 40

Workarounds I have tried

I also tried wrapping the shortcuts in backticks inside the <kbd>'ctrl'</kbd> (using real backticks of course), but confluence didn't display them using the fixed-width font (probably due to the missing spaces around the backticks):
image

Adding spaces around the backticks would modify the markdown output again, so this is where I stopped and opened this issue.

Do you have an idea on how to work around this? Or would it be feasible to support this use-case directly?

Feature request: using _index.md instead of empty pages

Hello,

I am using HUGO for generating a documentation site for markdown files. We are making extensive use of _index.md files for the 'index-page' of a directory.

Would it be possible to use these _index.md pages instead of creating an empty file for each directory? This would make it easier to fill in empty spaces and still get the requested directory structure. Thanks!

Example directory structure:

docs
Development
_index.md
docker.md

On confluence I would like to have a Development page with the content of _index.md and a Docker page for docker.md. Currently when I upload this structure it's complaining about double pages because I have the Title for _index.md set to Development

Associate Markdown files and page ID's

Related to #20.

To make it easier to update pages and change titles, I think it'd be helpful to save the relationship of a Markdown file and its page ID (preferably the whole URL) after creating it. I've seen this done with a separate config file, front matter, and comments. My preference would be to append a comment to the end of the file, e.g.:

# Page title

Page content

<!-- https://ORG.atlassian.net/wiki/spaces/~SPACE_ID/pages/PAGE_ID/Page+title -->

This could then be parsed for the host, space, and page ID.

index.md as parent page

Is there any way to make the index.md page the parent page for a directory when uploading a directory?

I've tried setting the title of the index.md file to be the same as the parent directory but it errors out because another page already exists with that name.

❌ Some documents have the same title, but all Confluence pages in the same space must have different titles.                                                                                                                           
                                                                                                                                                                                                                                        
These are the documents (and path, if available) with identical titles:                                                                                                                                                                 
                                                                                                                                                                                                                                        
  Title          File                                                                                                                                                                                                                   
 ───────────────────────────────────────────────────────────────────────────────────                                                                                                                                                    
  Architecture   None                                                                                                                                                                                                                   
  Architecture   /Users/thed1360/Projects/new_project/docs/Architecture/index.md   

URL not found

Hello.

I'm trying to publish a test page to the Confluence using the tool. The command looks like this, plus username and password of course.

md2cf -o https://jayconfluence.atlassian.net -s MARKDOWN test.md

As a result I'm getting the 404 page, mentioning that the following URL is not found:
https://jayconfluence.atlassian.net/content?title=Markdown+Syntax&spaceKey=MARKDOWN

Indeed, if I go there - it is not found. The URL to the existing page looks like this:
https://jayconfluence.atlassian.net/wiki/spaces/MARKDOWN/pages/262145/Markdown+Syntax

I get the same trying to create non-existent page. Interestingly, I also get the same response if I provide wrong password, for example.

Did the URL change or am I doing something wrong?

Thanks in advance!

.pages file at top (root) level

I'd like to be able to specify a pages file at the top level. This is useful in the following scenario:

I have two folders A and B, both containing markdown files, both containing links to each other.

I wish to process A and B into folders ie there should be a parent page for A, and a parent page for B.

Because there are relative links between A and B, A and B must be processed in a single run. The --parent-title option doesn't help because the files from A and from B would be mixed underneath a single parent page.

If A and B are specified on the command line, the .pages files are ignored, because A and B are currently processed as separate "root level" directories.

I cannot specify the real root of A and B, because that would pull in additional directories and markdown files that aren't desired.

I have a proposed fix for this issue which I'll reference shortly...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.