From 4e4b0997a2172e00c8a46acb6889fe2c8c939a09 Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Mon, 27 Sep 2021 22:02:49 -0500 Subject: Add packaging tooling --- .gitignore | 2 +- commands.py | 113 ------------------------------------------- config.py | 6 --- custom_requests.py | 30 ------------ graphql_queries.py | 41 ---------------- main.py | 52 -------------------- ncurses.py | 106 ---------------------------------------- pyproject.toml | 16 ++++++ requirements.txt | 2 - setup.cfg | 28 +++++++++++ util.py | 63 ------------------------ wikijscmd/__init__.py | 11 +++++ wikijscmd/__main__.py | 4 ++ wikijscmd/cli.py | 52 ++++++++++++++++++++ wikijscmd/commands.py | 113 +++++++++++++++++++++++++++++++++++++++++++ wikijscmd/config.py | 6 +++ wikijscmd/custom_requests.py | 30 ++++++++++++ wikijscmd/graphql_queries.py | 41 ++++++++++++++++ wikijscmd/ncurses.py | 103 +++++++++++++++++++++++++++++++++++++++ wikijscmd/util.py | 63 ++++++++++++++++++++++++ 20 files changed, 468 insertions(+), 414 deletions(-) delete mode 100644 commands.py delete mode 100644 config.py delete mode 100644 custom_requests.py delete mode 100644 graphql_queries.py delete mode 100755 main.py delete mode 100755 ncurses.py create mode 100644 pyproject.toml delete mode 100644 requirements.txt create mode 100644 setup.cfg delete mode 100644 util.py create mode 100644 wikijscmd/__init__.py create mode 100644 wikijscmd/__main__.py create mode 100755 wikijscmd/cli.py create mode 100644 wikijscmd/commands.py create mode 100644 wikijscmd/config.py create mode 100644 wikijscmd/custom_requests.py create mode 100644 wikijscmd/graphql_queries.py create mode 100755 wikijscmd/ncurses.py create mode 100644 wikijscmd/util.py diff --git a/.gitignore b/.gitignore index a254dfa..0cab0f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ __pycache__/ config.ini -*.swp +dist/ diff --git a/commands.py b/commands.py deleted file mode 100644 index 4d941cf..0000000 --- a/commands.py +++ /dev/null @@ -1,113 +0,0 @@ -import sys - -import graphql_queries -from datetime import datetime, timedelta -from 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/config.py b/config.py deleted file mode 100644 index b42ca6e..0000000 --- a/config.py +++ /dev/null @@ -1,6 +0,0 @@ -import os -from configparser import ConfigParser - -config_path = os.path.join(os.path.dirname(__file__), "config.ini") -config = ConfigParser() -config.read(config_path) diff --git a/custom_requests.py b/custom_requests.py deleted file mode 100644 index 65673cf..0000000 --- a/custom_requests.py +++ /dev/null @@ -1,30 +0,0 @@ -import requests -import sys -import json -from 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/graphql_queries.py b/graphql_queries.py deleted file mode 100644 index db23f23..0000000 --- a/graphql_queries.py +++ /dev/null @@ -1,41 +0,0 @@ -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/main.py b/main.py deleted file mode 100755 index c91fc1c..0000000 --- a/main.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import argparse - -from config import config -from commands import create, edit, single, tree, today, move, fill_in_pages - -def main(): - 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/ncurses.py b/ncurses.py deleted file mode 100755 index 073ad50..0000000 --- a/ncurses.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 -import main -import curses -from curses import wrapper - -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 = main.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 - -# Run the ncurses wrapper -try: - wrapper(m) -except Exception as e: - raise e diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..44ab690 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "wikijscmd" +version = "0.1.0" +description = "A command line interface (CLI) for wiki.js" +authors = ["Mark Powers "] +license = "GPL 3" + +[tool.poetry.dependencies] +python = "^3.8" +requests = "^2.26.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 57db5b3..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests==2.25.1 - diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..58f4a0d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,28 @@ +[metadata] +name = wikijscmd-markpowers +version = 0.0.1 +author = Mark Powers +author_email = author@example.com +description = A command line interface client for wiki.js +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/Mark-Powers/wikijscmd +project_urls = + Bug Tracker = https://github.com/Mark-Powers/wikijscmd/issues +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + Operating System :: OS Independent + +[options] +package_dir = + = wikijscmd +packages = find: +python_requires = >=3.6 + +[options.packages.find] +where=wikijscmd + +[entry_points] +console_scripts = + wikijscmd = wikijscmd.cli:cli() diff --git a/util.py b/util.py deleted file mode 100644 index a3b9fbd..0000000 --- a/util.py +++ /dev/null @@ -1,63 +0,0 @@ -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"), - } 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"), + } -- cgit v1.2.3