aboutsummaryrefslogtreecommitdiff
path: root/wikijscmd
diff options
context:
space:
mode:
Diffstat (limited to 'wikijscmd')
-rw-r--r--wikijscmd/__init__.py11
-rw-r--r--wikijscmd/__main__.py4
-rwxr-xr-xwikijscmd/cli.py52
-rw-r--r--wikijscmd/commands.py113
-rw-r--r--wikijscmd/config.py6
-rw-r--r--wikijscmd/custom_requests.py30
-rw-r--r--wikijscmd/graphql_queries.py41
-rwxr-xr-xwikijscmd/ncurses.py103
-rw-r--r--wikijscmd/util.py63
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"),
+ }