diff options
author | Mark Powers <mark@marks.kitchen> | 2021-09-27 22:02:49 -0500 |
---|---|---|
committer | Mark Powers <mark@marks.kitchen> | 2021-09-27 22:02:49 -0500 |
commit | 4e4b0997a2172e00c8a46acb6889fe2c8c939a09 (patch) | |
tree | 8ccc07bff1ae7f98a34d6f18d47b3595ebc4712b /wikijscmd | |
parent | 4493e31a1afaf49187fc9a151982279d83b71f65 (diff) |
Add packaging tooling
Diffstat (limited to 'wikijscmd')
-rw-r--r-- | wikijscmd/__init__.py | 11 | ||||
-rw-r--r-- | wikijscmd/__main__.py | 4 | ||||
-rwxr-xr-x | wikijscmd/cli.py | 52 | ||||
-rw-r--r-- | wikijscmd/commands.py | 113 | ||||
-rw-r--r-- | wikijscmd/config.py | 6 | ||||
-rw-r--r-- | wikijscmd/custom_requests.py | 30 | ||||
-rw-r--r-- | wikijscmd/graphql_queries.py | 41 | ||||
-rwxr-xr-x | wikijscmd/ncurses.py | 103 | ||||
-rw-r--r-- | wikijscmd/util.py | 63 |
9 files changed, 423 insertions, 0 deletions
diff --git a/wikijscmd/__init__.py b/wikijscmd/__init__.py new file mode 100644 index 0000000..8f45ae2 --- /dev/null +++ b/wikijscmd/__init__.py @@ -0,0 +1,11 @@ +from wikijscmd import cli +from wikijscmd import ncurses + +def main(): + cli.cli() + +def tui(): + try: + ncurses.wrapper(m) + except Exception as e: + raise e diff --git a/wikijscmd/__main__.py b/wikijscmd/__main__.py new file mode 100644 index 0000000..02ddab3 --- /dev/null +++ b/wikijscmd/__main__.py @@ -0,0 +1,4 @@ +import wikijscmd + +if __name__ == '__main__': + wikijscmd.main() diff --git a/wikijscmd/cli.py b/wikijscmd/cli.py new file mode 100755 index 0000000..2847e40 --- /dev/null +++ b/wikijscmd/cli.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +import sys +import argparse + +from wikijscmd.config import config +from wikijscmd.commands import create, edit, single, tree, today, move, fill_in_pages + +def cli(): + parser = argparse.ArgumentParser("wikijscmd") + parser.set_defaults(command=None) + subparsers = parser.add_subparsers() + + parser_create = subparsers.add_parser("create", help="create a page") + parser_create.add_argument("path", type=str, help="the path of the new page") + parser_create.add_argument("title", type=str, help="the title of the new page") + parser_create.add_argument("content", nargs="?", type=str, help="optional page content") + parser_create.set_defaults(command=create) + + parser_tree = subparsers.add_parser("tree", help="search in the page tree") + parser_tree.add_argument("regex", type=str, help="optional regex to search paths with") + parser_tree.set_defaults(command=tree) + + parser_single = subparsers.add_parser("single", help="view a single page") + parser_single.add_argument("path", type=str, help="the path of the page to view") + parser_single.set_defaults(command=single) + + parser_edit = subparsers.add_parser("edit", help="edit a page") + parser_edit.add_argument("path", type=str, help="the path of the page to edit") + parser_edit.set_defaults(command=edit) + + parser_today = subparsers.add_parser("today", help="create/edit the journal page for today") + parser_today.set_defaults(command=today) + + parser_move = subparsers.add_parser("move", help="move a page") + parser_move.add_argument("source", type=str, help="the path of the page to move") + parser_move.add_argument("dest", type=str, help="the destination path") + parser_move.set_defaults(command=move) + + parser_journal = subparsers.add_parser("journal", help="create journal pages") + parser_journal.set_defaults(command=fill_in_pages) + + args = vars(parser.parse_args()) + callback = args["command"] + if callback is None: + parser.print_help() + else: + del args["command"] + callback(**args) + +if __name__ == "__main__": + main() diff --git a/wikijscmd/commands.py b/wikijscmd/commands.py new file mode 100644 index 0000000..86cfe7b --- /dev/null +++ b/wikijscmd/commands.py @@ -0,0 +1,113 @@ +import sys + +from wikijscmd import graphql_queries +from datetime import datetime, timedelta +from wikijscmd.util import clean_filename, get_tree, open_editor, get_single_page, print_item, args_for_date + +def create(path, title, content=None): + page = get_single_page(path) + if page is not None: + print("Page already exists with path: %s" % path) + if input("Edit it? (y/n) ") == "y": + edit(path) + return + if not content: + content = open_editor("create", path, "") + response = graphql_queries.create_page(content, title, path) + result = response["data"]["pages"]["create"]["responseResult"] + if not result["succeeded"]: + print("Error!", result["message"]) + sys.exit(1) + print(result["message"]) + +def tree(regex): + """ + Finds pages based on a path search + """ + for item in get_tree(regex): + print_item(item) + +def single(path, raw=False): + """ + View a page with the given path + """ + page = get_single_page(path) + if page is None: + print("No page with path: %s" % path) + sys.exit(1) + if raw: + print("-" * 80) + print_item(page) + print("-" * 80) + print(page["content"]) + +def move(source, dest): + """ + Move a page from one path to another + """ + page = get_single_page(source) + if page is None: + print("Source page %s does not exist" % source) + sys.exit(1) + response = graphql_queries.move_page(page["id"], dest) + result = response["data"]["pages"]["move"]["responseResult"] + if not result["succeeded"]: + print("Error!", result["message"]) + sys.exit(1) + print(result["message"]) + +def edit(path, save=False): + """ + Edit a page + """ + page = get_single_page(path) + if page is None: + print("No page with path: %s" % path) + if input("Create it? (y/n) ") == "y": + title = input("Enter the title: ").strip() + create(path, title) + return + body = page["content"] + + # Open it in editor + new_body = open_editor("edit", path, body) + + # Prompt user to save it to the wiki + print_item(page) + print("-" * 80) + print(new_body) + print("-" * 80) + if save or input("Save changes? (y/n) ") == "y": + response = graphql_queries.edit_page(page["id"], new_body, page["title"], page["path"]) + result = response["data"]["pages"]["update"]["responseResult"] + if not result["succeeded"]: + print("Error!", result["message"]) + sys.exit(1) + print(result["message"]) + +def fill_in_pages(): + last_date = None + for page in get_tree("journal"): + try: + date = datetime.strptime(page["path"], "journal/%Y/%b/%d") + if last_date is None or date > last_date: + last_date = date + except ValueError: + continue + today = datetime.now().date() + if last_date is None: + last_date = today + pending_date = last_date.date() + while pending_date < today: + pending_date += timedelta(days=1) + create(**args_for_date(pending_date)) + +def today(): + """ + Creates a journal page with the path "journal/YYYY/MM/DD" + """ + args = args_for_date(datetime.now().date()) + if get_single_page(args["path"]) is not None: + edit(args["path"]) + else: + create(**args) diff --git a/wikijscmd/config.py b/wikijscmd/config.py new file mode 100644 index 0000000..f6c9b7e --- /dev/null +++ b/wikijscmd/config.py @@ -0,0 +1,6 @@ +import os +from configparser import ConfigParser + +config_path = "/etc/wikijscmd/config.ini" +config = ConfigParser() +config.read(config_path) diff --git a/wikijscmd/custom_requests.py b/wikijscmd/custom_requests.py new file mode 100644 index 0000000..bc9b16e --- /dev/null +++ b/wikijscmd/custom_requests.py @@ -0,0 +1,30 @@ +import requests +import sys +import json +from wikijscmd.config import config + +def handle_errors(r): + error = False + if r.status_code != 200: + error = True + print("Error status code: %s" % r.status_code) + json = r.json() + if "errors" in json: + error = True + for e in json["errors"]: + print(e["message"]) + print(e) + if error: + sys.exit(1) + + +def get_headers(): + return { "Authorization": "Bearer %s" % config["wiki"]["key"] } + +def send_query(query, query_vars): + '''Returns status code, json''' + payload = { "query": query, "variables": query_vars} + r = requests.post(config["wiki"]["url"], json=payload, headers = get_headers()) + handle_errors(r) + return r.json() + diff --git a/wikijscmd/graphql_queries.py b/wikijscmd/graphql_queries.py new file mode 100644 index 0000000..32aaaef --- /dev/null +++ b/wikijscmd/graphql_queries.py @@ -0,0 +1,41 @@ +from wikijscmd import custom_requests + +def get_single_page(page_id): + query = 'query ($id: Int!) {\npages {\nsingle (id: $id) {\nid\npath\ntitle\ncontent\n}\n}\n}' + query_vars = {"id": page_id} + return custom_requests.send_query(query, query_vars) + +def create_page(content, title, path): + query = 'mutation ($content: String!, $path: String!, $title: String!) {\npages {\ncreate(\ncontent: $content\ndescription: ""\neditor: "markdown"\nisPrivate: false\nisPublished: true\nlocale: "en"\npath: $path\npublishEndDate: ""\npublishStartDate: ""\nscriptCss: ""\nscriptJs: ""\ntags: []\ntitle: $title\n) {\nresponseResult {\nsucceeded\nerrorCode\nslug\nmessage\n__typename\n}\npage {\nid\nupdatedAt\n__typename\n}\n__typename\n}\n__typename\n}\n}' + query_vars = {"content": content, "title": title, "path": path} + return custom_requests.send_query(query, query_vars) + +def get_tree(): + query = 'query {\n pages {\n list (orderBy: PATH) {\n id\npath\ntitle\n}\n}\n}' + query_vars = { } + return custom_requests.send_query(query, query_vars) + +def edit_page(page_id, content, title, path): + query = 'mutation ($id: Int!, $content: String!, $path: String!, $title: String!){\npages {\nupdate(\nid: $id\ncontent: $content\ndescription: ""\neditor: "markdown"\nisPrivate: false\nisPublished: true\nlocale: "en"\npath: $path\npublishEndDate: ""\npublishStartDate: ""\nscriptCss: ""\nscriptJs: ""\ntags: []\ntitle: $title\n) {\nresponseResult {\nsucceeded\nerrorCode\nslug\nmessage\n__typename\n}\npage {\nid\nupdatedAt\n__typename\n}\n__typename\n}\n__typename\n}\n}' + query_vars = {"id": page_id, "content": content, "title": title, "path": path} + return custom_requests.send_query(query, query_vars) + +def move_page(page_id, destination_path): + query = '''mutation ($id: Int!, $destinationPath: String!, $destinationLocale: String!) { + pages { + move(id: $id, destinationPath: $destinationPath, destinationLocale: $destinationLocale) { + responseResult { + succeeded + errorCode + slug + message + __typename + } + __typename + } + __typename + } + }''' + query_vars = {"id": page_id, "destinationPath": destination_path, "destinationLocale": "en"} + return custom_requests.send_query(query, query_vars) + diff --git a/wikijscmd/ncurses.py b/wikijscmd/ncurses.py new file mode 100755 index 0000000..ecc145f --- /dev/null +++ b/wikijscmd/ncurses.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +import curses +from curses import wrapper + +from wikijscmd import util + +def pager(stdscr, lst): + ''' + Runs a pager for each string item in lst + ''' + cols = stdscr.getmaxyx()[1] + rows = stdscr.getmaxyx()[0] + offset = 0 + selected = 0 + while True: + stdscr.clear() + for i in range(min(rows-1, len(lst))): + x = lst[i+offset] + if i+offset == selected: + stdscr.addstr(i, 0, x[:cols], curses.A_UNDERLINE) + else: + stdscr.addstr(i, 0, x[:cols]) + if offset == 0: + stdscr.addstr(rows-1, 0, "--top--", curses.A_REVERSE) + elif offset + rows <= len(lst): + stdscr.addstr(rows-1, 0, "--more--", curses.A_REVERSE) + else: + stdscr.addstr(rows-1, 0, "--end--", curses.A_REVERSE) + k = stdscr.getch() + if k == curses.KEY_DOWN or k == ord('j'): + selected = min(len(lst), selected+1) + if (selected - offset) > (2 * rows / 3): + offset = min(len(lst)-rows+1, offset+1) + elif k == curses.KEY_UP or k == ord('k'): + selected = max(0, selected-1) + if (selected - offset) < (rows / 3): + offset = max(0, offset-1) + elif k == curses.KEY_NPAGE: + offset = min(len(lst)-rows+1, offset+rows-2) + selected = min(len(lst)-rows+1, selected+rows-2) + elif k == curses.KEY_PPAGE: + offset = max(0, offset-rows+2) + selected = max(0, selected-rows+2) + elif k == curses.KEY_HOME: + offset = 0 + selected = 0 + elif k == curses.KEY_END: + offset = len(lst)-rows+1 + selected = len(lst)-1 + elif k == curses.KEY_ENTER or k == 10: + return {"index": selected, "action": "select"} + elif k == ord('q'): + return {"index": selected, "action": "quit"} + elif k == ord('e'): + return {"index": selected, "action": "edit"} + elif k == ord('c'): + return {"index": selected, "action": "create"} + elif k == ord('t'): + return {"index": selected, "action": "today"} + stdscr.refresh() + +def enter_value(stdscr, prefix, row): + """ + Creates a prompt to enter a value on the given row + """ + title = "" + stdscr.addstr(row,0, prefix + title) + k = stdscr.getch() + while k != 10 and k != curses.KEY_ENTER: + if k in (curses.KEY_BACKSPACE, '\b', '\x7f'): + if len(title) > 0: + title = title[:-1] + else: + title += chr(k) + stdscr.deleteln() + stdscr.addstr(row,0, prefix + title) + k = stdscr.getch() + return title + +def m(stdscr): + """ + The main method for the ncurses wrapper + """ + items = util.get_tree("") + while True: + ret = pager(stdscr, [x["path"] + "\t" + x["title"] for x in items]) + if ret["action"] == "select": + selected = items[ret["index"]] + ret = pager(stdscr, main.get_single_page(selected["path"])["content"].split("\n")) + elif ret["action"] == "edit": + selected = items[ret["index"]] + main.edit({"path":selected["path"], "save": True}) + elif ret["action"] == "create": + stdscr.clear() + title = enter_value(stdscr, "Enter title: ", 0) + path = enter_value(stdscr, "Enter path: ", 1) + main.create({"path": path, "title": title}) + elif ret["action"] == "today": + main.today({}) + else: + break + diff --git a/wikijscmd/util.py b/wikijscmd/util.py new file mode 100644 index 0000000..86f2eb4 --- /dev/null +++ b/wikijscmd/util.py @@ -0,0 +1,63 @@ +from wikijscmd import graphql_queries +import subprocess +import re +import os + +def print_item(item): + trimmmed_path = item["path"][:17]+"..." if len(item["path"]) > 20 else item["path"] + print("| %6s | %20s | %44s |" % (item["id"], trimmmed_path, item["title"]) ) + +def get_single_page(path): + """ + Gets the page from the wiki with the given path + """ + if path.startswith("/"): + path = path[1:] + for item in graphql_queries.get_tree()["data"]["pages"]["list"]: + if path == item["path"]: + page_id = int(item["id"]) + response = graphql_queries.get_single_page(page_id) + return response["data"]["pages"]["single"] + return None + +def get_tree(regex): + response = graphql_queries.get_tree() + pages = [] + for item in response["data"]["pages"]["list"]: + if not re.search(regex, item["path"]): + continue + pages.append(item) + return pages + +def clean_filename(pathname): + """ + Clean the path so that it can be used as a filename + """ + pathname = str(pathname).strip().replace('/', '_') + pathname = re.sub(r'\W', '', pathname) + return pathname[:200] + +def open_editor(action, pathname, initial_body): + """ + Open a page with the given pathname and intial_body in an editor, using + action in the filename. Returns the content of the edited file. + """ + if "VISUAL" in os.environ: + editor = os.environ['VISUAL'] + else: + editor = os.environ['EDITOR'] + filename = "/tmp/wikijscmd-"+action+"-"+clean_filename(pathname)+".md" + if len(initial_body) > 0: + with open(filename, "w") as f: + f.write(initial_body) + subprocess.run([editor, filename]) + with open(filename, "r") as f: + new_body = f.read() + os.remove(filename) + return new_body + +def args_for_date(date): + return { + "path": date.strftime("journal/%Y/%b/%d").lower(), + "title": date.strftime("%B %-d"), + } |