#!/usr/bin/env python3 """ License: MIT Author: Bryan Newbold Date: July 2017 See README """ from __future__ import print_function import sys, os import argparse import requests import subprocess import logging as log class DivergenceProgram: def __init__(self, user, password, url, space): self.api = requests.Session() self.api.auth = (user, password) self.api.headers.update({'Content-Type': 'application/json'}) self.base_url = url self.space = space self.pandoc_helper_path = None for p in ('./pandoc_confluence.lua', '/usr/local/lib/divergence/pandoc_confluence.lua', '/usr/lib/divergence/pandoc_confluence.lua'): if os.path.exists(p): self.pandoc_helper_path = p break if self.pandoc_helper_path is None: log.error("Could not find pandoc helper (pandoc_confluence.lua), bailing") sys.exit(-1) def get_page(self, title): """ Returns None if not found, otherwise a dict with id, space, and body (in storage format) """ # TODO: could remove the body_view stuff here resp = self.api.get(self.base_url + "/rest/api/content", params={"spaceKey": self.space, "title": title, "expand": "body.storage,body.view,version,space", "type": "page"}) log.debug(resp) log.debug(resp.content) assert resp.status_code == 200 respj = resp.json() if respj['size'] == 0: return None assert respj['size'] == 1, "Expect single result for title lookup" page = respj['results'][0] assert page['space']['key'].upper() == self.space.upper(), "Expect spaces to match" return {"id": int(page['id']), "version": int(page['version']['number']), "space": page['space']['key'], "body": page['body']['storage']['value'], "body_view": page['body']['view']['value']} def create_page(self, title, body): resp = self.api.post(self.base_url + "/rest/api/content", json={"space": { "key": self.space }, "type": "page", "title": title, "body": { "storage": { "representation": "storage", "value": body } } } ) log.debug(resp) log.debug(resp.content) assert resp.status_code == 200 def update_page(self, title, body, page_id, prev_version): resp = self.api.put(self.base_url + "/rest/api/content/%d" % page_id, json={"type": "page", "title": title, "version": {"number": prev_version+1}, "body": { "storage": { "representation": "storage", "value": body } } } ) log.debug(resp) log.debug(resp.content) assert resp.status_code == 200 def title_from_path(self, path): title = path.split('.')[0].replace('_', ' ') # TODO: only alphanum and spaces? return title def convert(self, f): proc = subprocess.run(["pandoc", "-t", self.pandoc_helper_path, f], stdout=subprocess.PIPE) assert proc.returncode == 0 return proc.stdout.decode('UTF-8') def run(self, files): for f in files: title = self.title_from_path(f) log.debug(title) body = self.convert(f) prev = self.get_page(title) log.debug(prev) if prev is None: self.create_page(title, body) print(f + ": created") else: if prev['body'] != body: # TODO: too much changes in the diff here. Should do # something like store the file sha1 in a comment, regex # that out, and compare? from difflib import Differ sys.stdout.writelines(Differ().compare( prev['body'].splitlines(keepends=True), body.splitlines(keepends=True))) self.update_page(title, body, prev['id'], prev['version']) print(f + ": updated") else: print(f + ": no change") def main(): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=""" Simple Markdown-to-Confluence uploader, using pandoc and the Confluence REST API. required environment variables: CONFLUENCE_USER CONFLUENCE_PASSWORD CONFLUENCE_URL """) #usage="%(prog)s [options] -s ") parser.add_argument("-v", "--verbose", action="count", default=0, help="Show more debugging statements (can be repeated)") parser.add_argument("-s", "--space-key", default=None, help='Confluence Space Key (usually like "PROJ" or "~username")') parser.add_argument("FILE", nargs='+') args = parser.parse_args() if args.verbose > 0: log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) else: log.basicConfig(format="%(levelname)s: %(message)s") try: user = os.environ['CONFLUENCE_USER'] password = os.environ['CONFLUENCE_PASSWORD'] url = os.environ['CONFLUENCE_URL'] except KeyError: parser.exit(-1, "Need to pass environment variable configs\n") log.info("User: " + user) log.info("URL: " + url) if url.endswith('/'): url = url[:-1] if args.space_key is None: args.space_key = "~" + user log.warn("Defaulting to home space: %s" % args.space_key) try: subprocess.check_output(['pandoc', '--version']) except: parser.exit(-1, "This script depends on 'pandoc', which doesn't " "seem to be installed.\n") dp = DivergenceProgram(user, password, url, args.space_key) dp.run(args.FILE) if __name__ == '__main__': main()