def main(): parser = argparse.ArgumentParser() parser.add_argument("file", type=str, help="Pandoc's Markdown file path.") args = parser.parse_args() file = pathlib.Path(args.file) # Read Pandoc markwdown file. with file.open('r', encoding='utf_8_sig') as fd: lines = fd.readlines() # Extract YAML metadata block from Pandoc markdown. # http://pandoc.org/README.html#extension-yaml_metadata_block metadata_content = '' if len(lines) > 0 and lines[0] == '---\n': lines.pop(0) while True: if len(lines) == 0: raise Exception('No YAML metadata block end') line = lines.pop(0) if line in ('...\n', '---\n'): metadata_end_line = line break metadata_content += line else: raise Exception('No YAML metadata block') yaml_preserve_order() metadata = yaml.safe_load(metadata_content) confluenceMetadata = metadata.get(CONFLUENCE) # type: Optional[dict] if confluenceMetadata is None: raise Exception( 'No `{0}` section in YAML metadata block'.format(CONFLUENCE)) # Parse username, baseUrl, pageId, spaceKey, title from the metadata. if CONFLUENCE_PAGE_URL in confluenceMetadata: urlstr = confluenceMetadata[CONFLUENCE_PAGE_URL] url = urllib.parse.urlsplit(urlstr) # type: urllib.parse.SplitResult path = pathlib.PurePosixPath(url.path) query = urllib.parse.parse_qs(url.query) plen = len(path.parts) username = url.username if plen >= 4 and path.parts[ plen - 3] == 'display': # e.g. ['/', 'confluence', 'display', '~jsmith', 'Test+page'] basePath = str(path.parents[2]).rstrip('/') pageId = None spaceKey = urllib.parse.unquote_plus(path.parts[plen - 2]) title = urllib.parse.unquote_plus(path.parts[plen - 1]) elif plen >= 3 and path.parts[plen - 2] == 'pages' and path.parts[ plen - 1] == 'viewpage.action': # e.g. ['/', 'confluence', 'pages', 'viewpage.action'] basePath = str(path.parents[1]).rstrip('/') pageId = int(query['pageId'][0]) spaceKey = None title = None else: raise Exception( 'Unknown Confluence page URL format: {0}'.format(urlstr)) elif CONFLUENCE_BASE_URL in confluenceMetadata: urlstr = confluenceMetadata[CONFLUENCE_BASE_URL] url = urllib.parse.urlsplit(urlstr) # type: urllib.parse.SplitResult username = url.username basePath = url.path.rstrip('/') pageId = None spaceKey = None title = None else: raise Exception( 'No `{0}` or `{1}` in `{2}` section of YAML metadata block'.format( CONFLUENCE_PAGE_URL, CONFLUENCE_BASE_URL, CONFLUENCE)) baseUrlWithUsername = urllib.parse.urlunsplit( (url.scheme, url.netloc, basePath, None, None)) baseUrl = urllib.parse.urlunsplit( (url.scheme, url.netloc.rpartition("@")[2], basePath, None, None)) newTitle = metadata.get('title') # type: Optional[str] authors = metadata.get('author', []) # type: List[str] # Set default user name. if username is None: if len(authors) > 0: author = authors[0] firstname, lastname = author.split() # type: str username = firstname[0].lower() + lastname.lower() else: username = getpass.getuser() # Set default space key. if spaceKey is None: spaceKey = '~' + username # Convert Pandoc's Markdown file to Confluence Storage Format (CSF) using `pandoc` utility. cmd = [ "pandoc", "--from=markdown+hard_line_breaks+lists_without_preceding_blankline+compact_definition_lists+smart+autolink_bare_uris", "--to", os.path.join(os.path.dirname(sys.argv[0]), "csf.lua"), str(file) ] res = subprocess.run(cmd, stdout=subprocess.PIPE) content = res.stdout.decode('utf-8') if confluenceMetadata.get(CONFLUENCE_NOTE_AUTOGEN, False): # type: bool note = textwrap.dedent("""\ <ac:structured-macro ac:name="info" ac:schema-version="1"> <ac:rich-text-body><p>This page is generated automatically from <ac:link><ri:attachment ri:filename="{filename}"/></ac:link> using <a href="{project_url}">{project_name}</a>.</p></ac:rich-text-body> </ac:structured-macro> """).format(filename=html.escape(file.name), project_name=html.escape(PROJECT_NAME), project_url=html.escape(PROJECT_URL)) content = note + content # Ask username and password. confluence = Confluence(baseUrl, username) # Request Confluence API to edit or create a page. try: info = confluence.post_page(pageId, spaceKey, title, newTitle, content) except requests.exceptions.HTTPError as ex: response = ex.response # type: requests.models.Response if response.status_code == 401: print('Authentication failed.') else: print(ex) print(response.text) return else: if info is None: return # Update metadata. confluenceMetadata.pop(CONFLUENCE_BASE_URL, None) confluenceMetadata[ CONFLUENCE_PAGE_URL] = baseUrlWithUsername + info['_links']['webui'] confluenceMetadata[CONFLUENCE_PAGE_VERSION] = info['version']['number'] # Rewrite Pandoc markdown file with updated YAML metadata block. fd = tempfile.NamedTemporaryFile('w', encoding='utf_8', delete=False, dir=str(file.parent), suffix='.tmp') # type: io.TextIOWrapper with fd: fd.write('---\n') yaml.dump(metadata, fd, default_flow_style=False, allow_unicode=True) fd.write(metadata_end_line) fd.writelines(lines) os.replace(fd.name, str(file)) # src and dst are on the same filesystem # Attach source file. try: confluence.attach_file(info, file, content_type="text/markdown", comment="Source code of this page.") except requests.exceptions.HTTPError as ex: response = ex.response # type: requests.models.Response print(ex) print(response.text)
def main() -> None: usage = ( '\n{0} --baseurl BASEURL [--user USER] ([--space SPACE] --title TITLE | --pageid PAGEID) [--new-title NEW_TITLE] ([--file FILE] | --text TEXT)' '\n{0} --baseurl BASEURL [--user USER] ([--space SPACE] --new-title NEW_TITLE ([--file FILE] | --text TEXT)' '\n{0} (-h | --help)') parser = argparse.ArgumentParser( usage=usage.format(os.path.basename(sys.argv[0]))) parser.add_argument( "--baseurl", required=True, help='Conflunce base URL. Format: https://example.com/confluence') parser.add_argument( "--user", default=None, help= "User name to log into Confluence. Default: from the environment or password database." ) parser.add_argument("--pageid", type=int, help="Conflunce page id to edit page.") parser.add_argument( "--space", default=None, help= "Conflunce space key to create/edit page. Default: the user's home.") parser.add_argument("--title", default=None, help="Conflunce page title to edit page.") parser.add_argument( "--new-title", default=None, help= "New title to create/edit page. By default title is not changed on edit page." ) group = parser.add_mutually_exclusive_group() group.add_argument( "--file", type=argparse.FileType('r'), default=sys.stdin, help="Write the content of FILE to the confluence page. Default: STDIN." ) group.add_argument( "--text", help= "Write the TEXT in Confluence Storage Format (XHTML-like) to the confluence page." ) args = parser.parse_args() if (args.pageid, args.title, args.new_title).count(None) == 3: parser.print_usage() exit() if args.space is None: args.space = '~' + args.user if args.text is not None: content = args.text else: content = args.file.read() confluence = Confluence(args.baseurl, args.user) confluence.post_page(args.pageid, args.space, args.title, args.new_title, content)