#!/usr/bin/env python3 # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # Copyright (c) KALEIDOS INC import argparse import json import socket import sys from tabulate import tabulate from getpass import getpass from urllib.parse import urlparse PREPL_URI = "tcp://localhost:6063" def get_prepl_conninfo(): uri_data = urlparse(PREPL_URI) if uri_data.scheme != "tcp": raise RuntimeError(f"invalid PREPL_URI: {PREPL_URI}") if not isinstance(uri_data.netloc, str): raise RuntimeError(f"invalid PREPL_URI: {PREPL_URI}") host, port = uri_data.netloc.split(":", 2) if port is None: port = 6063 if isinstance(port, str): port = int(port) return host, port def send_eval(expr): host, port = get_prepl_conninfo() with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) s.send(expr.encode("utf-8")) s.send(b":repl/quit\n\n") with s.makefile() as f: while True: line = f.readline() result = json.loads(line) tag = result.get("tag", None) if tag == "ret": return result.get("val", None), result.get("exception", None) elif tag == "out": print(result.get("val"), end="") else: raise RuntimeError("unexpected response from PREPL") def encode(val): return json.dumps(json.dumps(val)) def print_error(res): for error in res["via"]: print("ERR:", error["message"]) break def run_cmd(params): try: expr = "(app.srepl.cli/exec {})".format(encode(params)) res, failed = send_eval(expr) if failed: print_error(res) sys.exit(-1) return res except Exception as cause: print("EXC:", str(cause)) sys.exit(-2) def create_profile(fullname, email, password): params = { "cmd": "create-profile", "params": { "fullname": fullname, "email": email, "password": password } } res = run_cmd(params) print(f"Created: {res['email']} / {res['id']}") def update_profile(email, fullname, password, is_active): params = { "cmd": "update-profile", "params": { "email": email, "fullname": fullname, "password": password, "is_active": is_active } } res = run_cmd(params) if res is True: print(f"Updated") else: print(f"No profile found with email {email}") def delete_profile(email, soft): params = { "cmd": "delete-profile", "params": { "email": email, "soft": soft } } res = run_cmd(params) if res is True: print(f"Deleted") else: print(f"No profile found with email {email}") def search_profile(email): params = { "cmd": "search-profile", "params": { "email": email, } } res = run_cmd(params) if isinstance(res, list): print(tabulate(res, headers="keys")) def derive_password(password): params = { "cmd": "derive-password", "params": { "password": password, } } res = run_cmd(params) print(f"Derived password: \"{res}\"") def migrate_components_v2(): params = { "cmd": "migrate-v2", "params": {} } run_cmd(params) available_commands = ( "create-profile", "update-profile", "delete-profile", "search-profile", "derive-password", ) parser = argparse.ArgumentParser( description=( "Penpot Command Line Interface (CLI)" ) ) parser.add_argument("-V", "--version", action="version", version="Penpot CLI %%develop%%") parser.add_argument("action", action="store", choices=available_commands) parser.add_argument("-f", "--force", help="force operation", action="store_true") parser.add_argument("-n", "--fullname", help="fullname", action="store") parser.add_argument("-e", "--email", help="email", action="store") parser.add_argument("-p", "--password", help="password", action="store") parser.add_argument("-c", "--connect", help="connect to PREPL", action="store", default="tcp://localhost:6063") args = parser.parse_args() PREPL_URI = args.connect if args.action == "create-profile": email = args.email password = args.password fullname = args.fullname if email is None: email = input("Email: ") if fullname is None: fullname = input("Fullname: ") if password is None: password = getpass("Password: ") create_profile(fullname, email, password) elif args.action == "update-profile": email = args.email password = args.password if email is None: email = input("Email: ") if password is None: password = getpass("Password: ") update_profile(email, None, password, None) elif args.action == "derive-password": password = args.password if password is None: password = getpass("Password: ") derive_password(password) elif args.action == "delete-profile": email = args.email soft = not args.force if email is None: email = input("Email: ") delete_profile(email, soft) elif args.action == "search-profile": email = args.email if email is None: email = input("Email: ") search_profile(email)