async def interactive_shell(): """ Like `interactive_shell`, but doing things manual. """ # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop() # Create interface. cli = CommandLineInterface( application=create_prompt_application('Say something inside the event loop: '), eventloop=eventloop ) # Patch stdout in something that will always print *above* the prompt when # something is written to stdout. sys.stdout = cli.stdout_proxy() # Run echo loop. Read text from stdin, and reply it back. while True: try: result = await cli.run_async() print('You said: "{0}"'.format(result.text)) except (EOFError, KeyboardInterrupt): return
def interactive_shell(): """ Coroutine that shows the interactive command line. """ # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop() # Create interface. cli = CommandLineInterface(application=create_default_application( 'Say something inside the event loop: '), eventloop=eventloop) # Patch stdout in something that will always print *above* the prompt when # something is written to stdout. sys.stdout = cli.stdout_proxy() # Run echo loop. Read text from stdin, and reply it back. while True: try: result = yield from cli.run_async() print('You said: "%s"\n' % result.text) except (EOFError, KeyboardInterrupt): loop.stop() print('Qutting event loop. Bye.') return
async def td_client_interactive_shell(callback): eventloop = create_asyncio_eventloop() key_bindings_manager = KeyBindingManager.for_prompt() load_td_client_kay_bindings(key_bindings_manager=key_bindings_manager) cli = CommandLineInterface( application=create_prompt_application('待开发指令,just echo: ', key_bindings_registry=key_bindings_manager.registry, get_bottom_toolbar_tokens=get_td_client_bottom_toolbar_tokens, style=get_td_client_styles() ), eventloop=eventloop ) sys.stdout = cli.stdout_proxy() while True: try: result = await cli.run_async() if callback is not None: resp = callback(user_input=result.text) print(resp) else: print('You said: "{0}"'.format(result.text)) except (EOFError, KeyboardInterrupt): return
def interactive_shell(): """ Coroutine that shows the interactive command line. """ # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop() # Create interface. cli = CommandLineInterface( application=create_default_application('Say something inside the event loop: '), eventloop=eventloop) # Patch stdout in something that will always print *above* the prompt when # something is written to stdout. sys.stdout = cli.stdout_proxy() # Run echo loop. Read text from stdin, and reply it back. while True: try: result = yield from cli.run_async() print('You said: "%s"\n' % result.text) except (EOFError, KeyboardInterrupt): loop.stop() print('Qutting event loop. Bye.') return
class UI(object): manager = KeyBindingManager() buffers = { DEFAULT_BUFFER: Buffer(is_multiline=True), 'MESSAGES': Buffer(is_multiline=True), } layout = VSplit([ Window( dont_extend_width=True, content=TokenListControl( get_tokens=lambda cli: [(Token, 'SlackTerm')], ), ), Window( width=D.exact(1), content=FillControl('|', token=Token.Line), ), HSplit([ Window(content=BufferControl(buffer_name='MESSAGES')), Window( height=D.exact(1), content=FillControl('_', token=Token.Line), ), Window( height=D.exact(3), content=BufferControl(buffer_name=DEFAULT_BUFFER), ), ]), ]) def __init__(self): application = Application( buffers=self.buffers, layout=self.layout, key_bindings_registry=self.manager.registry, mouse_support=True, use_alternate_screen=True, ) eventloop = create_asyncio_eventloop() self.cli = CommandLineInterface(application=application, eventloop=eventloop) sys.stdout = self.cli.stdout_proxy() async def start(self): while True: result = await self.cli.run_async() if not result: break @manager.registry.add_binding(Keys.ControlC, eager=True) def exit_(self): self.cli.set_return_value(None)
async def client_talk(loop, coro): usr = await prompt_async("username:"******"password:"******">" contacts = [] history = InMemoryHistory() cli = CommandLineInterface(application=create_prompt_application( cin, history=history, completer=cmd_complete, get_bottom_toolbar_tokens=toolbar_tokens), eventloop=loop) # cli = create_prompt_application( # cin,history=history, # completer=cmd_complete, # get_bottom_toolbar_tokens=toolbar_tokens,patch_stdout=True) sys.stdout = cli.stdout_proxy() client[0].send(Login(usr, pwd)) cin = usr + ">" while True: try: msg = await cli.run_async() sys.stdout.flush() msg = msg.text try: if msg.startswith("/"): msg = msg.split(sep="/") if msg[1] == "bye": client[0].close_conn() elif msg[1] == "contacts": client[0].send(Request("contacts", usr)) elif msg[1] == "send": client[0].send(Sender(msg[2], msg[3])) print("{0:s} [{1:s}]: {2:s}\n".format( tstamp(), usr, msg[3])) else: raise IndexError except IndexError: print("Not understood.") continue except (EOFError, KeyboardInterrupt): return
class Session: def __init__(self, host): self.host = host self.socket = None self.cli = CommandLineInterface( application=create_prompt_application('> '), eventloop=create_asyncio_eventloop()) sys.stdout = self.cli.stdout_proxy() async def connect(self): self.socket = await ws.connect(self.host) async def send(self): while self.socket.open: try: result = await self.cli.run_async() if result.text: await self.socket.send(result.text) except ws.ConnectionClosed: print('! send closed') break except KeyboardInterrupt: print('! interrupted') break async def recv(self): while self.socket.open: try: result = await self.socket.recv() print('<', result) except ws.ConnectionClosed: print('! recv closed') break async def loop(self): await self.connect() done, pending = await asyncio.wait( [self.send(), self.recv()], return_when=futures.FIRST_COMPLETED) for task in done: try: task.result() except Exception as e: import traceback traceback.print_exc() await self.socket.close() if pending: await asyncio.wait(pending)
async def server_console(loop): history = InMemoryHistory() cli = CommandLineInterface( application=create_prompt_application('> ', completer=cmd_complete, history=history, get_bottom_toolbar_tokens=toolbar_tokens), eventloop=loop) sys.stdout = cli.stdout_proxy() # stdout fd is asynchronous, so print messages as they arrive while True: try: result = await cli.run_async() # await a command, "await" is an asyncio-specific thing for client in clients: # Maybe you wanna send everyone a message about something client.send(result.text) except (EOFError, KeyboardInterrupt): return
async def server_console(loop): history = InMemoryHistory() cli = CommandLineInterface(application=create_prompt_application( '> ', completer=cmd_complete, history=history, get_bottom_toolbar_tokens=toolbar_tokens), eventloop=loop) sys.stdout = cli.stdout_proxy() while True: try: result = await cli.run_async() for client in clients: client.send(result.text) except (EOFError, KeyboardInterrupt): return
async def interactive_shell(bot): eventloop = create_asyncio_eventloop(bot.loop) cli = CommandLineInterface( application=create_prompt_application(':> ', completer=myCompleter(bot)), eventloop=eventloop, ) sys.stdout = cli.stdout_proxy() while True: try: result = await cli.run_async() if result.text.startswith("#"): parse = total.parseString(result.text[1:], parseAll=True) try: resp = await bot.run_parse(parse, Message(server='SHELL')) for res in resp: print(res.text) except PipeError as e: for loc, err in sorted(e.exs, key=lambda x: x[0]): if isinstance(err, PipeError): for loc2, err2 in sorted(err.exs, key=lambda x: x[0]): try: raise err2 except Exception: # how to print to terminal traceback.print_exc() else: try: raise err except Exception: # how to print to terminal traceback.print_exc() else: try: await aexec(result.text, local={'bot': bot}) except Exception: traceback.print_exc() except (EOFError, KeyboardInterrupt): bot.loop.stop()
async def interactive_shell(): """ Like `interactive_shell`, but doing things manual. """ # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop() # Create interface. cli = CommandLineInterface(application=create_prompt_application( 'Say something inside the event loop: '), eventloop=eventloop) # Patch stdout in something that will always print *above* the prompt when # something is written to stdout. sys.stdout = cli.stdout_proxy() # Run echo loop. Read text from stdin, and reply it back. while True: try: result = await cli.run_async() print('You said: "{0}"'.format(result.text)) except (EOFError, KeyboardInterrupt): return
class SimpleTestInterface(object): def __init__(self, repository, eventloop, tests, config, em, runner_class): self.repository = repository self.history = InMemoryHistory() self.tests = tests self.config = config self.displayer = TestDisplayer(self.tests) self.eventloop = eventloop self.runner_class = runner_class self.em = em # Register the callbacks self.em.register(self.displayer.parse_message) self.application = create_prompt_application("> ", history=self.history, completer=completer) self.cli = CommandLineInterface( application=self.application, eventloop=create_asyncio_eventloop(eventloop)) sys.stdout = self.cli.stdout_proxy() def run(self): self.eventloop.run_until_complete(self._run()) async def _run(self): while True: try: result = await self.cli.run_async() except (EOFError, KeyboardInterrupt): return command = result.text if command == "p": self.tests.status() elif command == "pf": self.tests.failed_tests() elif command == "r": await self.launch_all_tests() elif command == "f": await self.launch_failed_tests() self.tests.status_by_status() async def launch_all_tests(self): session = self._get_runner([default_test_args]) await session.run() async def launch_failed_tests(self): tests = self.tests.get_test_by_outcome("failed") session = self._get_runner(tests) await session.run() def _get_runner(self, tests): return self.runner_class(self.config, self.repository, self.em, tests, loop=self.eventloop)
import sys import logging import importlib logging.basicConfig(level='ERROR') import concurrent import asyncio import unha2.client as client from unha2.model.base import RoomType from unha2.methods import MethodError eventloop = create_asyncio_eventloop() cli = CommandLineInterface( application=create_prompt_application('> '), eventloop=eventloop) sys.stdout = cli.stdout_proxy() STYLE_DICT = { Token.Title: 'bold', Token.Error: '#ansired', Token.Nick: '#ansiyellow', Token.Server: '#ansiblue', Token.Chat: '#ansigreen', Token.Direct: '#ansifuchsia', Token.Private: '#ansiblue', Token.Time: '#ansilightgray', Token.Separator: '#ansilightgray bold', } STYLE = style_from_dict(STYLE_DICT)
class Cli: isElectionStarted = False primariesSelected = 0 electedPrimaries = set() # noinspection PyPep8 def __init__(self, looper, tmpdir, nodeReg, cliNodeReg): self.curClientPort = None logging.root.addHandler(CliHandler(self.out)) self.looper = looper self.tmpdir = tmpdir self.nodeReg = nodeReg self.cliNodeReg = cliNodeReg # Used to store created clients self.clients = {} # clientId -> Client # To store the created requests self.requests = {} # To store the nodes created self.nodes = {} self.cliCmds = {'new', 'status', 'list'} self.nodeCmds = {'new', 'status', 'list', 'keyshare'} self.helpablesCommands = self.cliCmds | self.nodeCmds self.simpleCmds = {'status', 'help', 'exit', 'quit', 'license'} self.commands = {'list'} | self.simpleCmds self.cliActions = {'send', 'show'} self.commands.update(self.cliCmds) self.commands.update(self.nodeCmds) self.node_or_cli = ['node', 'client'] self.nodeNames = list(self.nodeReg.keys()) + ["all"] ''' examples: status new node Alpha new node all new client Joe client Joe send <msg> client Joe show 1 ''' def re(seq): return '(' + '|'.join(seq) + ')' grams = [ "(\s* (?P<simple>{}) \s*) |".format(re(self.simpleCmds)), "(\s* (?P<command>{}) \s*) |".format(re(self.commands)), "(\s* (?P<client_command>{}) \s+ (?P<node_or_cli>clients?) \s+ (?P<client_name>[a-zA-Z0-9]+) \s*) |".format(re(self.cliCmds)), "(\s* (?P<node_command>{}) \s+ (?P<node_or_cli>nodes?) \s+ (?P<node_name>[a-zA-Z0-9]+) \s*) |".format(re(self.nodeCmds)), "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>send) \s+ (?P<msg>\{\s*\".*\}) \s*) |", "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>show) \s+ (?P<req_id>[0-9]+) \s*) " # "(\s* (?P<command>help) \s+ (?P<helpable>[a-zA-Z0-9]+) \s*) |", # "(\s* (?P<command>[a-z]+) \s+ (?P<arg1>[a-zA-Z0-9]+) \s*) |", # "(\s* (?P<command>[a-z]+) \s+ (?P<arg1>client) \s+ (?P<arg2>[a-zA-Z0-9]+) \s*) |", # "(\s* (?P<command>[a-z]+) \s+ (?P<arg1>[a-zA-Z0-9]+) \s+ (?P<arg2>\{\s*\".*\}) \s*) |", # "(\s* (?P<client_name>[a-z]+) \s+ (?P<cli_action>[a-zA-Z0-9]+) \s+ (?P<msg>\{\s*\".*\}) \s*) " ] self.grammar = compile("".join(grams)) lexer = GrammarLexer(self.grammar, lexers={ 'node_command': SimpleLexer(Token.Keyword), 'command': SimpleLexer(Token.Keyword), 'node_or_cli': SimpleLexer(Token.Keyword), 'arg2': SimpleLexer(Token.Name), 'node_name': SimpleLexer(Token.Name), 'simple': SimpleLexer(Token.Keyword), 'client_command': SimpleLexer(Token.Keyword), }) self.clientWC = WordCompleter([]) completer = GrammarCompleter(self.grammar, { 'node_command': WordCompleter(self.nodeCmds), 'client_command': WordCompleter(self.cliCmds), 'client': WordCompleter(['client']), 'command': WordCompleter(self.commands), 'node_or_cli': WordCompleter(self.node_or_cli), 'node_name': WordCompleter(self.nodeNames), 'helpable': WordCompleter(self.helpablesCommands), 'client_name': self.clientWC, 'cli_action': WordCompleter(self.cliActions), 'simple': WordCompleter(self.simpleCmds) }) self.style = PygmentsStyle.from_defaults({ Token.Operator: '#33aa33 bold', Token.Number: '#aa3333 bold', Token.Name: '#ffff00 bold', Token.Heading: 'bold', Token.TrailingInput: 'bg:#662222 #ffffff', Token.BoldGreen: '#33aa33 bold', Token.BoldOrange: '#ff4f2f bold', Token.BoldBlue: '#095cab bold'}) self.functionMappings = self.createFunctionMappings() # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop(looper.loop) pers_hist = FileHistory('.zeno-cli-history') # Create interface. app = create_prompt_application('zeno> ', lexer=lexer, completer=completer, style=self.style, history=pers_hist) self.cli = CommandLineInterface( application=app, eventloop=eventloop, output=CustomOutput.from_pty(sys.__stdout__, true_color=True)) # Patch stdout in something that will always print *above* the prompt # when something is written to stdout. sys.stdout = self.cli.stdout_proxy() setupLogging(TRACE_LOG_LEVEL, Console.Wordage.mute, filename="log/cli.log") self.logger = getlogger("cli") print("\nzeno-CLI (c) 2016 Evernym, Inc.") print("Node registry loaded.") print("None of these are created or running yet.") self.showNodeRegistry() print("Type 'help' for more information.") def createFunctionMappings(self): def newHelper(): print("""Is used to create a new node or a client. Usage: new <node/client> <nodeName/clientName>""") def statusHelper(): print("status command helper") def clientHelper(): print("Can be used to create a new client") def listHelper(): print("List all the commands, you can use in this CLI.") def exitHelper(): print("Exits the CLI") def licenseHelper(): print(""" Copyright 2016 Evernym, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """) def sendmsgHelper(): print("""Used to send a message from a client to nodes" Usage: sendmsg <clientName> <{Message}>""") def getreplyHelper(): print("""Used to send a message from a client to nodes" Usage: getreply <clientName> <reqID>""") def showdetailsHelper(): print("""Used to send a message from a client to nodes" Usage: showdetails <clientName> <reqID>""") def defaultHelper(): self.printHelp() mappings = { 'new': newHelper, 'status': statusHelper, 'list': listHelper, 'client': clientHelper, 'license': licenseHelper, 'sendmsg': sendmsgHelper, 'getreply': getreplyHelper, 'showdetails': showdetailsHelper, 'exit': exitHelper } return defaultdict(lambda: defaultHelper, **mappings) def print(self, msg, token=None, newline=True): if newline: msg += "\n" part = partial(self.cli.print_tokens, [(token, msg)]) self.cli.run_in_terminal(part) def out(self, record, extra_cli_value=None): """ Callback so that this cli can manage colors :param record: a log record served up from a custom handler :param extra_cli_value: the "cli" value in the extra dictionary :return: """ # self.trackElectionStarted(record) # self.trackElectionCompleted(record) # TODO come up with an encoding scheme if extra_cli_value in ("IMPORTANT", "ANNOUNCE"): self.print(record.msg, Token.BoldGreen) # green elif extra_cli_value in ("WARNING",): self.print(record.msg, Token.BoldOrange) # orange elif extra_cli_value in ("STATUS",): self.print(record.msg, Token.BoldBlue) # blue elif extra_cli_value in ("PLAIN", "LOW_STATUS"): self.print(record.msg, Token) # white else: self.print(record.msg, Token) @staticmethod def printHelp(): print("""zeno-CLI, a simple command-line interface for an zeno protocol sandbox. Commands: help - Shows this help message help <command> - Shows the help message of <command> new - creates a new node or client keyshare - manually starts key sharing of a node status - Shows general status of the sandbox status <node_name>|<client_name> - Shows specific status list - Shows the list of commands you can run license - Show the license exit - exit the command-line interface ('quit' also works)""") def printCmdHelper(self, command=None): self.functionMappings[command]() @staticmethod def joinTokens(tokens, separator=None, begin=None, end=None): if separator is None: separator = (Token, ', ') elif isinstance(separator, str): separator = (Token, separator) r = reduce(lambda x, y: x + [separator, y] if x else [y], tokens, []) if begin is not None: b = (Token, begin) if isinstance(begin, str) else begin r = [b] + r if end: if isinstance(end, str): r.append((Token, end)) return r def printTokens(self, tokens, separator=None, begin=None, end=None): x = self.joinTokens(tokens, separator, begin, end) self.cli.print_tokens(x, style=self.style) def printNames(self, names, newline=False): tokens = [(Token.Name, n) for n in names] self.printTokens(tokens) if newline: self.printTokens([(Token, "\n")]) def showValidNodes(self): self.printTokens([(Token, "Valid node names are: ")]) self.printNames(self.nodeReg.keys(), newline=True) def showNodeRegistry(self): t = [] for n, (ip, port) in self.nodeReg.items(): t.append((Token.Name, " " + n)) t.append((Token, ": {}:{}\n".format(ip, port))) self.cli.print_tokens(t, style=self.style) def getStatus(self): if len(self.nodes) > 1: print("The following nodes are up and running: ") elif len(self.nodes) == 1: print("The following node is up and running: ") else: print("No nodes are running. Try typing 'new node <name>'.") for node in self.nodes: print(node) if len(self.nodes) > 1: print("The following clients are up and running: ") elif len(self.nodes) == 1: print("The following client is up and running: ") else: print("No clients are running. Try typing 'new client <name>'.") for client in self.clients: print(client) def keyshare(self, nodeName): node = self.nodes[nodeName] node.startKeySharing() def newNode(self, nodeName): if nodeName in self.nodes: print("Node {} already exists.\n".format(nodeName)) return if nodeName == "all": names = set(self.nodeReg.keys()) - set(self.nodes.keys()) elif nodeName not in self.nodeReg: tokens = [(Token.Error, "Invalid node name '{}'. ".format(nodeName))] self.printTokens(tokens) self.showValidNodes() return else: names = [nodeName] for name in names: node = Node(name, self.nodeReg, basedirpath=self.tmpdir) self.nodes[name] = node self.looper.add(node) node.startKeySharing() for client in self.clients.values(): self.bootstrapClientKey(client, node) def ensureValidClientId(self, clientId): """ Ensures client id is not already used or is not starting with node names. :param clientId: :return: """ if clientId in self.clients: raise ValueError("Client {} already exists.\n".format(clientId)) if any([clientId.startswith(nm) for nm in self.nodeNames]): raise ValueError("Client name cannot start with node names, " "which are {}\n" .format(', '.join(self.nodeReg.keys()))) def statusClient(self, clientId): if clientId == "all": for nm in self.clients: self.statusClient(nm) return if clientId not in self.clients: self.print("client not found", Token.Error) else: client = self.clients[clientId] # type: Client self.printTokens([(Token.Heading, 'Status for client:'), (Token.Name, client.name)], separator=' ', end='\n') self.print(" age (seconds): {:.0f}".format(time.perf_counter() - client.created)) self.print(" connected to: ", newline=False) if client._conns: self.printNames(client._conns, newline=True) else: self.print("<none>") self.print(" identifier: {}".format(client.signer.identifier)) self.print(" verification key: {}".format(client. signer.verkey)) self.print(" submissions: {}".format(client.lastReqId)) def statusNode(self, nodeName): if nodeName == "all": for nm in self.nodes: self.statusNode(nm) return if nodeName not in self.nodes: self.print("node not found", Token.Error) else: node = self.nodes[nodeName] # type: Node self.printTokens([(Token.Heading, 'Status for node:'), (Token.Name, node.name)], separator=' ', end='\n') self.print(" status: {}".format(node.status.name)) self.print(" age (seconds): {:.0f}". format(time.perf_counter() - node.created)) self.print(" connected nodes: ", newline=False) if node._conns: self.printNames(node._conns, newline=True) else: self.print("<none>") self.print(" connected clients: ", newline=False) clis = node.clientstack.connecteds() if clis: self.printNames(clis, newline=True) else: self.print("<none>") self.print(" client verification keys: {}". format(node.clientAuthNr.clients)) def newClient(self, clientId): try: self.ensureValidClientId(clientId) client_addr = self.getNextAvailableAddr() client = Client(clientId, ha=client_addr, nodeReg=self.cliNodeReg, basedirpath=self.tmpdir) self.looper.add(client) for node in self.nodes.values(): self.bootstrapClientKey(client, node) self.clients[clientId] = client self.clientWC.words = list(self.clients.keys()) except ValueError as ve: self.print(ve.args[0], Token.Error) @staticmethod def bootstrapClientKey(client, node): idAndKey = client.signer.identifier, client.signer.verkey node.clientAuthNr.addClient(*idAndKey) def sendMsg(self, clientName, msg): client = self.clients.get(clientName, None) if client: request, = client.submit(msg) self.requests[str(request.reqId)] = request.reqId else: print("No such client. See: 'help new' for more details") def getReply(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: reply, status = client.getReply(requestID) print("Reply for the request: {}\n".format(reply)) print("Status: {}\n".format(status)) elif not client: print("No such client. See: 'help new' for more details") else: print("No such request. See: 'help new' for more details") def showDetails(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: client.showReplyDetails(requestID) else: print("No such client. See: 'help new' for more details") async def shell(self, *commands, interactive=True): """ Coroutine that runs command, including those from an interactive command line. :param commands: an iterable of commands to run first :param interactive: when True, this coroutine will process commands entered on the command line. :return: """ # First handle any commands passed in for command in commands: print("\nRunning command: '{}'...\n".format(command)) self.parse(command) # then handle commands from the prompt while interactive: try: result = await self.cli.run_async() self.parse(result.text) except (EOFError, KeyboardInterrupt, Exit): return print('Goodbye.') def parse(self, cmdText): m = self.grammar.match(cmdText) if m: matchedVars = m.variables() self.logger.info("CLI command entered: {}".format(cmdText), extra={"cli": False}) # Check for helper commands if matchedVars.get('simple'): cmd = matchedVars.get('simple') if cmd == "help": self.printHelp() elif cmd == 'status': self.getStatus() elif cmd == 'license': self.printCmdHelper('license') elif cmd in ['exit', 'quit']: raise Exit elif matchedVars.get('command') == 'help': arg1 = matchedVars.get('arg1') if arg1: self.printCmdHelper(command=arg1) else: self.printHelp() elif matchedVars.get('command') == 'list': for cmd in self.commands: print(cmd) # Check for new commands elif matchedVars.get('node_command') == 'new': name = matchedVars.get('node_name') self.newNode(name) elif matchedVars.get('node_command') == 'status': node = matchedVars.get('node_name') self.statusNode(node) elif matchedVars.get('node_command') == 'keyshare': name = matchedVars.get('node_name') self.keyshare(name) elif matchedVars.get('client_command') == 'new': client = matchedVars.get('client_name') self.newClient(client) elif matchedVars.get('client_command') == 'status': client = matchedVars.get('client_name') self.statusClient(client) elif matchedVars.get('client') == 'client': client_name = matchedVars.get('client_name') client_action = matchedVars.get('cli_action') if client_action == 'send': msg = matchedVars.get('msg') self.sendMsg(client_name, msg) elif client_action == 'show': req_id = matchedVars.get('req_id') self.getReply(client_name, req_id) else: self.printCmdHelper("sendmsg") # check for the showdetails commmand elif matchedVars.get('command') == 'showdetails': arg1 = matchedVars.get('arg1') arg2 = matchedVars.get('arg2') if arg1 and arg2: self.showDetails(arg1, arg2) else: self.printCmdHelper("showstatus") # Fall back to the help saying, invalid command. else: self.invalidCmd(cmdText) else: if cmdText != "": self.invalidCmd(cmdText) def invalidCmd(self, cmdText): print("Invalid command: '{}'\n".format(cmdText)) self.printCmdHelper(command=None) def getNextAvailableAddr(self): self.curClientPort = self.curClientPort or 8100 self.curClientPort += 1 return "127.0.0.1", self.curClientPort
async def xmpp_client(): try: with open(os.path.join(os.getenv("HOME"), ".xc.conf"), "r") as config_file: config = json.load(config_file) if "jid" not in config: raise Exception("No JID") except Exception as e: print( "Error reading configuration file.\n" + str(e) + "Please create ~/.xc.conf with content like:\n{\"jid\": \"[email protected]\"}" ) sys.exit(1) async def get_secret(jid, attempt): if attempt > 2: return None try: return await prompt_async("Secret (will not be stored): ", is_password=True) except: return None async def tls_handshake_callback(verifier): print("Warning: blindly accepting TLS certificate from %s" % verifier.transport.get_extra_info("server_hostname")) return True pin_store = PublicKeyPinStore() pin_store.import_from_json(config.get("pkps", {})) my_jid = aioxmpp.JID.fromstr(config["jid"]) security = aioxmpp.make_security_layer( get_secret, pin_store=pin_store, pin_type=PinType.PUBLIC_KEY, post_handshake_deferred_failure=tls_handshake_callback) client = aioxmpp.PresenceManagedClient(my_jid, security) presence = client.summon(aioxmpp.presence.Service) roster = client.summon(aioxmpp.roster.Service) class RosterItemAndCommandCompleter(Completer): def get_completions(self, document, complete_event): text = document.text if not text or " " in text or ":" in text: return if text[0] == "/": part = text[1:] for command in ("roster", "name", "add", "del", "help", "quit"): if command.startswith(part): yield Completion(command + " ", start_position=-len(part), display=command) elif roster is not None: for item in roster.items.values(): if item.name.startswith(text): yield Completion(item.name + ": ", start_position=-len(text), display=item.name) completer = RosterItemAndCommandCompleter() history = InMemoryHistory() next_recipient = None def get_prompt_tokens(_): return ((Token.Prompt, "%s> " % (next_recipient or "")), ) cli_app = create_prompt_application(get_prompt_tokens=get_prompt_tokens, completer=completer, reserve_space_for_menu=0, history=history, get_title=lambda: "xc") cli_loop = create_asyncio_eventloop() cli = CommandLineInterface(application=cli_app, eventloop=cli_loop) above_prompt = cli.stdout_proxy() def name_for_jid(jid): try: return roster.items[jid].name except: return str(jid) def peer_available(jid, presence): above_prompt.write("%s is now online\n" % name_for_jid(jid.bare())) presence.on_available.connect(peer_available) def peer_unavailable(jid, presence): above_prompt.write("%s is now offline\n" % name_for_jid(jid.bare())) presence.on_unavailable.connect(peer_unavailable) def message_received(msg): content = " ".join(msg.body.values()) if content: above_prompt.write("%s: %s\n" % (name_for_jid(msg.from_.bare()), content)) client.stream.register_message_callback("chat", None, message_received) try: async with client.connected() as stream: while True: try: document = await cli.run_async() except (KeyboardInterrupt, EOFError): break line = document.text if line.startswith("/"): try: command, *args = line[1:].split(" ") if command == "roster": rows = [(item.name, str(jid), item.subscription) for jid, item in roster.items.items() if jid != my_jid] widths = [ max(len(row[i] or "") for row in rows) for i in range(len(rows[0] or "")) ] for row in rows: above_prompt.write(" ".join( (cell or "").ljust(widths[i]) for i, cell in enumerate(row)) + "\n") elif command == "name": try: jid = args[0] name = args[1] except IndexError: above_prompt.write("usage: /name JID NAME\n") else: jid = aioxmpp.JID.fromstr(jid) await roster.set_entry(jid, name=name) elif command == "add": try: jid = args[0] except IndexError: above_prompt.write("usage: /add JID\n") else: jid = aioxmpp.JID.fromstr(jid) await roster.set_entry(jid) roster.subscribe(jid) roster.approve(jid) elif command == "del": try: jid = args[0] except IndexError: above_prompt.write("usage: /del JID\n") else: jid = aioxmpp.JID.fromstr(jid) await roster.remove_entry(jid) elif command == "quit": break elif command == "help": above_prompt.write( "NAME: MESSAGE send MESSAGE to NAME\n" "MESSAGE send MESSAGE to the last correspondent\n" "/roster print the roster\n" "/add JID add JID to the roster\n" "/del JID remove JID from the roster\n" "/name JID NAME set the name of JID to NAME\n" "/quit disconnect and then quit (also ctrl-d, ctrl-c)\n" "/help this help\n") else: above_prompt.write("unrecognised command\n") except Exception as e: above_prompt.write("exception handling command: %s\n" % e) else: try: try: recipient, message = line.split(": ", 1) except ValueError: if next_recipient is not None: recipient = next_recipient message = line else: above_prompt.write("recipient: message\n") continue jid_for_name = lambda r: next( (jid for jid, item in roster.items.items() if item.name == r), None) jid = jid_for_name(recipient) if jid is None: if next_recipient is not None and recipient != next_recipient: recipient = next_recipient jid = jid_for_name(recipient) message = line if jid is None: above_prompt.write("unknown recipient: %s\n" % recipient) continue msg = aioxmpp.Message(to=jid, type_="chat") msg.body[None] = message await stream.send_and_wait_for_sent(msg) next_recipient = recipient except Exception as e: above_prompt.write("exception sending message: %s\n" % e) print("Disconnecting…") client.stop() except Exception as e: print("Failed to connect: %s" % e)
class Cli: isElectionStarted = False primariesSelected = 0 electedPrimaries = set() name = 'plenum' properName = 'Plenum' fullName = 'Plenum protocol' NodeClass = Node ClientClass = Client # noinspection PyPep8 def __init__(self, looper, basedirpath, nodeReg, cliNodeReg, output=None, debug=False, logFileName=None): self.curClientPort = None logging.root.addHandler(CliHandler(self.out)) self.looper = looper self.basedirpath = basedirpath self.nodeReg = nodeReg self.cliNodeReg = cliNodeReg # Used to store created clients self.clients = {} # clientName -> Client # To store the created requests self.requests = {} # To store the nodes created self.nodes = {} self.externalClientKeys = {} # type: Dict[str,str] self.cliCmds = {'status', 'new'} self.nodeCmds = self.cliCmds | {'keyshare'} self.helpablesCommands = self.cliCmds | self.nodeCmds self.simpleCmds = {'status', 'exit', 'quit', 'license'} self.commands = {'list', 'help'} | self.simpleCmds self.cliActions = {'send', 'show'} self.commands.update(self.cliCmds) self.commands.update(self.nodeCmds) self.node_or_cli = ['node', 'client'] self.nodeNames = list(self.nodeReg.keys()) + ["all"] self.debug = debug self.plugins = {} ''' examples: status new node Alpha new node all new client Joe client Joe send <msg> client Joe show 1 ''' psep = re.escape(os.path.sep) self.utilGrams = [ "(\s* (?P<simple>{}) \s*) |".format(self.relist(self.simpleCmds)), "(\s* (?P<load>load) \s+ (?P<file_name>[.a-zA-z0-9{}]+) \s*) |".format(psep), "(\s* (?P<command>help) (\s+ (?P<helpable>[a-zA-Z0-9]+) )? (\s+ (?P<node_or_cli>{}) )?\s*) |".format(self.relist(self.node_or_cli)), "(\s* (?P<command>list) \s*)" ] self.nodeGrams = [ "(\s* (?P<node_command>{}) \s+ (?P<node_or_cli>nodes?) \s+ (?P<node_name>[a-zA-Z0-9]+)\s*) |".format(self.relist(self.nodeCmds)), "(\s* (?P<load_plugins>load\s+plugins\s+from) \s+ (?P<plugin_dir>[a-zA-Z0-9-:{}]+) \s*)".format(psep), ] self.clientGrams = [ "(\s* (?P<client_command>{}) \s+ (?P<node_or_cli>clients?) \s+ (?P<client_name>[a-zA-Z0-9]+) \s*) |".format(self.relist(self.cliCmds)), "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>send) \s+ (?P<msg>\{\s*.*\}) \s*) |", "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>show) \s+ (?P<req_id>[0-9]+) \s*) |", "(\s* (?P<add_key>add\s+key) \s+ (?P<verkey>[a-fA-F0-9]+) \s+ (?P<for_client>for\s+client) \s+ (?P<identifier>[a-zA-Z0-9]+) \s*)", ] self.lexers = { 'node_command': SimpleLexer(Token.Keyword), 'command': SimpleLexer(Token.Keyword), 'helpable': SimpleLexer(Token.Keyword), 'load_plugins': SimpleLexer(Token.Keyword), 'load': SimpleLexer(Token.Keyword), 'node_or_cli': SimpleLexer(Token.Keyword), 'arg1': SimpleLexer(Token.Name), 'node_name': SimpleLexer(Token.Name), 'more_nodes': SimpleLexer(Token.Name), 'simple': SimpleLexer(Token.Keyword), 'client_command': SimpleLexer(Token.Keyword), 'add_key': SimpleLexer(Token.Keyword), 'verkey': SimpleLexer(Token.Literal), 'for_client': SimpleLexer(Token.Keyword), 'identifier': SimpleLexer(Token.Name), } self.clientWC = WordCompleter([]) self.completers = { 'node_command': WordCompleter(self.nodeCmds), 'client_command': WordCompleter(self.cliCmds), 'client': WordCompleter(['client']), 'command': WordCompleter(self.commands), 'node_or_cli': WordCompleter(self.node_or_cli), 'node_name': WordCompleter(self.nodeNames), 'more_nodes': WordCompleter(self.nodeNames), 'helpable': WordCompleter(self.helpablesCommands), 'load_plugins': WordCompleter(['load plugins from']), 'client_name': self.clientWC, 'cli_action': WordCompleter(self.cliActions), 'simple': WordCompleter(self.simpleCmds), 'add_key': WordCompleter(['add key']), 'for_client': WordCompleter(['for client']), } self.initializeGrammar() self.initializeGrammarLexer() self.initializeGrammarCompleter() self.style = PygmentsStyle.from_defaults({ Token.Operator: '#33aa33 bold', Token.Number: '#aa3333 bold', Token.Name: '#ffff00 bold', Token.Heading: 'bold', Token.TrailingInput: 'bg:#662222 #ffffff', Token.BoldGreen: '#33aa33 bold', Token.BoldOrange: '#ff4f2f bold', Token.BoldBlue: '#095cab bold'}) self.functionMappings = self.createFunctionMappings() self.voidMsg = "<none>" # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop(looper.loop) pers_hist = FileHistory('.{}-cli-history'.format(self.name)) # Create interface. app = create_prompt_application('{}> '.format(self.name), lexer=self.grammarLexer, completer=self.grammarCompleter, style=self.style, history=pers_hist) if output: out = output else: if is_windows(): if is_conemu_ansi(): out = ConEmuOutput(sys.__stdout__) else: out = Win32Output(sys.__stdout__) else: out = CustomOutput.from_pty(sys.__stdout__, true_color=True) self.cli = CommandLineInterface( application=app, eventloop=eventloop, output=out) # Patch stdout in something that will always print *above* the prompt # when something is written to stdout. sys.stdout = self.cli.stdout_proxy() setupLogging(TRACE_LOG_LEVEL, Console.Wordage.mute, filename=logFileName) self.logger = getlogger("cli") self.print("\n{}-CLI (c) 2016 Evernym, Inc.".format(self.properName)) self.print("Node registry loaded.") self.print("None of these are created or running yet.") self.showNodeRegistry() self.print("Type 'help' for more information.") @staticmethod def relist(seq): return '(' + '|'.join(seq) + ')' def initializeGrammar(self): self.utilGrams[-1] += " |" self.nodeGrams[-1] += " |" self.grams = self.utilGrams + self.nodeGrams + self.clientGrams self.grammar = compile("".join(self.grams)) def initializeGrammarLexer(self): self.grammarLexer = GrammarLexer(self.grammar, lexers=self.lexers) def initializeGrammarCompleter(self): self.grammarCompleter = GrammarCompleter(self.grammar, self.completers) def createFunctionMappings(self): def newHelper(): self.print("""Is used to create a new node or a client. Usage: new <node/client> <nodeName/clientName>""") def statusHelper(): self.print("status command helper") def nodeHelper(): self.print("It is used to create a new node") def clientHelper(): self.print("It is used to create a new client") def statusNodeHelper(): self.print("It is used to check status of a created node") def statusClientHelper(): self.print("It is used to check status of a created client") def listHelper(): self.print("List all the commands, you can use in this CLI.") def exitHelper(): self.print("Exits the CLI") def licenseHelper(): self.print(""" Copyright 2016 Evernym, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """) def sendHelper(): self.print("""Used to send a message from a client to nodes" Usage: client <clientName> send <{Message}>""") def showHelper(): self.print("""Used to show status of request sent by the client" Usage: client <clientName> show <reqID>""") def defaultHelper(): self.printHelp() def pluginHelper(): self.print("""Used to load a plugin from a given directory Usage: load plugins from <dir>""") mappings = { 'new': newHelper, 'status': statusHelper, 'list': listHelper, 'newnode': nodeHelper, 'newclient': clientHelper, 'statusnode': statusNodeHelper, 'statusclient': statusClientHelper, 'license': licenseHelper, 'send': sendHelper, 'show': showHelper, 'exit': exitHelper, 'plugins': pluginHelper } return defaultdict(lambda: defaultHelper, **mappings) def print(self, msg, token=None, newline=True): if newline: msg += "\n" part = partial(self.cli.print_tokens, [(token, msg)]) if self.debug: part() else: self.cli.run_in_terminal(part) def printVoid(self): self.print(self.voidMsg) def out(self, record, extra_cli_value=None): """ Callback so that this cli can manage colors :param record: a log record served up from a custom handler :param extra_cli_value: the "cli" value in the extra dictionary :return: """ if extra_cli_value in ("IMPORTANT", "ANNOUNCE"): self.print(record.msg, Token.BoldGreen) # green elif extra_cli_value in ("WARNING",): self.print(record.msg, Token.BoldOrange) # orange elif extra_cli_value in ("STATUS",): self.print(record.msg, Token.BoldBlue) # blue elif extra_cli_value in ("PLAIN", "LOW_STATUS"): self.print(record.msg, Token) # white else: self.print(record.msg, Token) def printHelp(self): self.print("""{}-CLI, a simple command-line interface for a {} sandbox. Commands: help - Shows this help message help <command> - Shows the help message of <command> new - creates one or more new nodes or clients keyshare - manually starts key sharing of a node status - Shows general status of the sandbox status <node_name>|<client_name> - Shows specific status list - Shows the list of commands you can run license - Show the license exit - exit the command-line interface ('quit' also works)""". format(self.properName, self.fullName)) def printCmdHelper(self, command=None): self.functionMappings[command]() @staticmethod def joinTokens(tokens, separator=None, begin=None, end=None): if separator is None: separator = (Token, ', ') elif isinstance(separator, str): separator = (Token, separator) r = reduce(lambda x, y: x + [separator, y] if x else [y], tokens, []) if begin is not None: b = (Token, begin) if isinstance(begin, str) else begin r = [b] + r if end: if isinstance(end, str): r.append((Token, end)) return r def printTokens(self, tokens, separator=None, begin=None, end=None): x = self.joinTokens(tokens, separator, begin, end) self.cli.print_tokens(x, style=self.style) def printNames(self, names, newline=False): tokens = [(Token.Name, n) for n in names] self.printTokens(tokens) if newline: self.printTokens([(Token, "\n")]) def showValidNodes(self): self.printTokens([(Token, "Valid node names are: ")]) self.printNames(self.nodeReg.keys(), newline=True) def showNodeRegistry(self): t = [] for name in self.nodeReg: val = self.nodeReg[name] if len(val) == 3: ((ip, port), vk, pk) = val else: (ip, port) = val t.append((Token.Name, " " + name)) t.append((Token, ": {}:{}\n".format(ip, port))) self.cli.print_tokens(t, style=self.style) def loadFromFile(self, file: str) -> None: cfg = ConfigParser() cfg.read(file) self.nodeReg = Cli.loadNodeReg(cfg) self.cliNodeReg = Cli.loadCliNodeReg(cfg) @classmethod def loadNodeReg(cls, cfg: ConfigParser) -> OrderedDict: return cls._loadRegistry(cfg, 'node_reg') @classmethod def loadCliNodeReg(cls, cfg: ConfigParser) -> OrderedDict: try: return cls._loadRegistry(cfg, 'client_node_reg') except configparser.NoSectionError: return OrderedDict() @classmethod def _loadRegistry(cls, cfg: ConfigParser, reg: str): registry = OrderedDict() for n in cfg.items(reg): host, port = n[1].split() registry.update({n[0]: (host, int(port))}) return registry def getStatus(self): self.print('Nodes: ', newline=False) if not self.nodes: self.print("No nodes are running. Try typing 'new node <name>'.") else: self.printNames(self.nodes, newline=True) if not self.clients: clients = "No clients are running. Try typing 'new client <name>'." else: clients = ",".join(self.clients.keys()) self.print("Clients: " + clients) f = getMaxFailures(len(self.nodes)) self.print("f-value (number of possible faulty nodes): {}".format(f)) if f != 0 and len(self.nodes) >= 2 * f + 1: node = list(self.nodes.values())[0] mPrimary = node.replicas[node.instances.masterId].primaryName bPrimary = node.replicas[node.instances.backupIds[0]].primaryName self.print("Instances: {}".format(f + 1)) self.print(" Master (primary is on {})". format(Replica.getNodeName(mPrimary))) self.print(" Backup (primary is on {})". format(Replica.getNodeName(bPrimary))) else: self.print("Instances: " "Not enough nodes to create protocol instances") def keyshare(self, nodeName): node = self.nodes.get(nodeName, None) if node is not None: node = self.nodes[nodeName] node.startKeySharing() elif nodeName not in self.nodeReg: tokens = [(Token.Error, "Invalid node name '{}'.".format(nodeName))] self.printTokens(tokens) self.showValidNodes() return else: tokens = [(Token.Error, "Node '{}' not started.".format(nodeName))] self.printTokens(tokens) self.showStartedNodes() return def showStartedNodes(self): self.printTokens([(Token, "Started nodes are: ")]) startedNodes = self.nodes.keys() if startedNodes: self.printNames(self.nodes.keys(), newline=True) else: self.print("None", newline=True) def newNode(self, nodeName: str): opVerifiers = self.plugins['VERIFICATION'] if self.plugins else [] if nodeName in self.nodes: self.print("Node {} already exists.".format(nodeName)) return if nodeName == "all": names = set(self.nodeReg.keys()) - set(self.nodes.keys()) elif nodeName not in self.nodeReg: tokens = [ (Token.Error, "Invalid node name '{}'. ".format(nodeName))] self.printTokens(tokens) self.showValidNodes() return else: names = [nodeName] nodes = [] for name in names: node = self.NodeClass(name, self.nodeReg, basedirpath=self.basedirpath, opVerifiers=opVerifiers) self.nodes[name] = node self.looper.add(node) node.startKeySharing() for client in self.clients.values(): self.bootstrapClientKey(client, node) for identifier, verkey in self.externalClientKeys.items(): node.clientAuthNr.addClient(identifier, verkey) nodes.append(node) return nodes def ensureValidClientId(self, clientName): """ Ensures client id is not already used or is not starting with node names. :param clientName: :return: """ if clientName in self.clients: raise ValueError("Client {} already exists.".format(clientName)) if any([clientName.startswith(nm) for nm in self.nodeNames]): raise ValueError("Client name cannot start with node names, " "which are {}." .format(', '.join(self.nodeReg.keys()))) def statusClient(self, clientName): if clientName == "all": for nm in self.clients: self.statusClient(nm) return if clientName not in self.clients: self.print("client not found", Token.Error) else: self.print(" Name: " + clientName) client = self.clients[clientName] # type: Client self.printTokens([(Token.Heading, 'Status for client:'), (Token.Name, client.name)], separator=' ', end='\n') self.print(" age (seconds): {:.0f}".format( time.perf_counter() - client.created)) self.print(" status: {}".format(client.status.name)) self.print(" connected to: ", newline=False) if client._conns: self.printNames(client._conns, newline=True) else: self.printVoid() self.print(" Identifier: {}".format(client.defaultIdentifier)) self.print(" Verification key: {}".format(client.getSigner().verkey)) self.print(" Submissions: {}".format(client.lastReqId)) def statusNode(self, nodeName): if nodeName == "all": for nm in self.nodes: self.statusNode(nm) return if nodeName not in self.nodes: self.print("Node {} not found".format(nodeName), Token.Error) else: self.print("\n Name: " + nodeName) node = self.nodes[nodeName] # type: Node val = self.nodeReg.get(nodeName) if len(val) == 3: ((ip, port), vk, pk) = val else: ip, port = val nha = "{}:{}".format(ip, port) self.print(" Node listener: " + nha) val = self.cliNodeReg.get(nodeName + CLIENT_STACK_SUFFIX) if len(val) == 3: ((ip, port), vk, pk) = val else: ip, port = val cha = "{}:{}".format(ip, port) self.print(" Client listener: " + cha) self.print(" Status: {}".format(node.status.name)) self.print(' Connections: ', newline=False) connecteds = node.nodestack.connecteds() if connecteds: self.printNames(connecteds, newline=True) else: self.printVoid() notConnecteds = list({r for r in self.nodes.keys() if r not in connecteds and r != nodeName}) if notConnecteds: self.print(' Not connected: ', newline=False) self.printNames(notConnecteds, newline=True) self.print(" Replicas: {}".format(len(node.replicas)), newline=False) if node.hasPrimary: if node.primaryReplicaNo == 0: self.print(" (primary of Master)") else: self.print(" (primary of Backup)") else: self.print(" (no primary replicas)") self.print(" Up time (seconds): {:.0f}". format(time.perf_counter() - node.created)) self.print(" Clients: ", newline=False) clients = node.clientstack.connecteds() if clients: self.printNames(clients, newline=True) else: self.printVoid() def newClient(self, clientName, seed=None, identifier=None, signer=None): try: self.ensureValidClientId(clientName) client_addr = self.nextAvailableClientAddr() if not signer: seed = seed.encode("utf-8") if seed else None signer = SimpleSigner(identifier=identifier, seed=seed) \ if (seed or identifier) else None client = self.ClientClass(clientName, ha=client_addr, nodeReg=self.cliNodeReg, signer=signer, basedirpath=self.basedirpath) self.looper.add(client) for node in self.nodes.values(): self.bootstrapClientKey(client, node) self.clients[clientName] = client self.clientWC.words = list(self.clients.keys()) return client except ValueError as ve: self.print(ve.args[0], Token.Error) @staticmethod def bootstrapClientKey(client, node): idAndKey = client.getSigner().identifier, client.getSigner().verkey node.clientAuthNr.addClient(*idAndKey) def sendMsg(self, clientName, msg): client = self.clients.get(clientName, None) if client: request, = client.submit(msg) self.requests[str(request.reqId)] = request.reqId else: self.print("No such client. See: 'help new' for more details") def getReply(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: reply, status = client.getReply(requestID) self.print("Reply for the request: {}".format(reply)) self.print("Status: {}".format(status)) elif not client: self.print("No such client. See: 'help new' for more details") else: self.print("No such request. See: 'help new' for more details") def showDetails(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: client.showReplyDetails(requestID) else: self.print("No such client. See: 'help new' for more details") async def shell(self, *commands, interactive=True): """ Coroutine that runs command, including those from an interactive command line. :param commands: an iterable of commands to run first :param interactive: when True, this coroutine will process commands entered on the command line. :return: """ # First handle any commands passed in for command in commands: self.print("\nRunning command: '{}'...\n".format(command)) self.parse(command) # then handle commands from the prompt while interactive: try: result = await self.cli.run_async() self.parse(result.text if result else "") except (EOFError, KeyboardInterrupt, Exit): break self.print('Goodbye.') def _simpleAction(self, matchedVars): if matchedVars.get('simple'): cmd = matchedVars.get('simple') if cmd == 'status': self.getStatus() elif cmd == 'license': self.printCmdHelper('license') elif cmd in ['exit', 'quit']: raise Exit return True def _helpAction(self, matchedVars): if matchedVars.get('command') == 'help': helpable = matchedVars.get('helpable') node_or_cli = matchedVars.get('node_or_cli') if helpable: if node_or_cli: self.printCmdHelper(command="{}{}". format(helpable, node_or_cli)) else: self.printCmdHelper(command=helpable) else: self.printHelp() return True def _listAction(self, matchedVars): if matchedVars.get('command') == 'list': for cmd in self.commands: self.print(cmd) return True def _newNodeAction(self, matchedVars): if matchedVars.get('node_command') == 'new': self.createEntities('node_name', 'more_nodes', matchedVars, self.newNode) return True def _newClientAction(self, matchedVars): if matchedVars.get('client_command') == 'new': self.createEntities('client_name', 'more_clients', matchedVars, self.newClient) return True def _statusNodeAction(self, matchedVars): if matchedVars.get('node_command') == 'status': node = matchedVars.get('node_name') self.statusNode(node) return True def _statusClientAction(self, matchedVars): if matchedVars.get('client_command') == 'status': client = matchedVars.get('client_name') self.statusClient(client) return True def _keyShareAction(self, matchedVars): if matchedVars.get('node_command') == 'keyshare': name = matchedVars.get('node_name') self.keyshare(name) return True def _clientCommand(self, matchedVars): if matchedVars.get('client') == 'client': client_name = matchedVars.get('client_name') client_action = matchedVars.get('cli_action') if client_action == 'send': msg = matchedVars.get('msg') try: actualMsgRepr = ast.literal_eval(msg) except Exception as ex: self.print("error evaluating msg expression: {}". format(ex), Token.BoldOrange) return True self.sendMsg(client_name, actualMsgRepr) return True elif client_action == 'show': req_id = matchedVars.get('req_id') self.getReply(client_name, req_id) return True # else: # self.printCmdHelper("sendmsg") def _loadPluginDirAction(self, matchedVars): if matchedVars.get('load_plugins') == 'load plugins from': pluginsPath = matchedVars.get('plugin_dir') try: self.plugins = PluginLoader(pluginsPath).plugins except FileNotFoundError as ex: _, err = ex.args self.print(err, Token.BoldOrange) return True def _loadPluginAction(self, matchedVars): if matchedVars.get('load') == 'load': file = matchedVars.get("file_name") if os.path.exists(file): try: self.loadFromFile(file) self.print("Node registry loaded.") self.showNodeRegistry() except configparser.ParsingError: self.logger.warn("Could not parse file. " "Please ensure that the file " "has sections node_reg " "and client_node_reg.", extra={'cli': 'WARNING'}) else: self.logger.warn("File {} not found.".format(file), extra={"cli": "WARNING"}) return True def _addKeyAction(self, matchedVars): if matchedVars.get('add_key') == 'add key': verkey = matchedVars.get('verkey') # TODO make verkey case insensitive identifier = matchedVars.get('identifier') if identifier in self.externalClientKeys: self.print("identifier already added", Token.Error) return self.externalClientKeys[identifier] = verkey for n in self.nodes.values(): n.clientAuthNr.addClient(identifier, verkey) return True def getActionList(self): return [self._simpleAction, self._helpAction, self._listAction, self._newNodeAction, self._newClientAction, self._statusNodeAction, self._statusClientAction, self._keyShareAction, self._loadPluginDirAction, self._clientCommand, self._loadPluginAction, self._addKeyAction] def parse(self, cmdText): m = self.grammar.match(cmdText) if m: matchedVars = m.variables() self.logger.info("CLI command entered: {}".format(cmdText), extra={"cli": False}) for action in self.getActionList(): r = action(matchedVars) if r: break else: self.invalidCmd(cmdText) else: if cmdText != "": self.invalidCmd(cmdText) @staticmethod def createEntities(name: str, moreNames: str, matchedVars, initializer): entity = matchedVars.get(name) more = matchedVars.get(moreNames) more = more.split(',') if more is not None and len(more) > 0 else [] names = [n for n in [entity] + more if len(n) != 0] seed = matchedVars.get("seed") identifier = matchedVars.get("nym") if len(names) == 1 and (seed or identifier): initializer(names[0].strip(), seed=seed, identifier=identifier) else: for name in names: initializer(name.strip()) def invalidCmd(self, cmdText): self.print("Invalid command: '{}'\n".format(cmdText)) self.printCmdHelper(command=None) def nextAvailableClientAddr(self, curClientPort=8100): self.curClientPort = self.curClientPort or curClientPort self.curClientPort += 1 host = "127.0.0.1" try: checkPortAvailable((host,self.curClientPort)) return host, self.curClientPort except Exception as ex: tokens = [(Token.Error, "Cannot bind to port {}: {}, " "trying another port.".format( self.curClientPort, ex))] self.printTokens(tokens) return self.nextAvailableClientAddr(self.curClientPort)
async def xmpp_client(): try: with open(os.path.join(os.getenv("HOME"), ".xc.conf"), "r") as config_file: config = json.load(config_file) if "jid" not in config: raise Exception("No JID") except Exception as e: print("Error reading configuration file.\n" + str(e) + "Please create ~/.xc.conf with content like:\n{\"jid\": \"[email protected]\"}") sys.exit(1) async def get_secret(jid, attempt): if attempt > 2: return None try: return await prompt_async("Secret (will not be stored): ", is_password=True) except: return None async def tls_handshake_callback(verifier): print("Warning: blindly accepting TLS certificate from %s" % verifier.transport.get_extra_info("server_hostname")) return True pin_store = PublicKeyPinStore() pin_store.import_from_json(config.get("pkps", {})) my_jid = aioxmpp.JID.fromstr(config["jid"]) security = aioxmpp.make_security_layer(get_secret, pin_store=pin_store, pin_type=PinType.PUBLIC_KEY, post_handshake_deferred_failure=tls_handshake_callback) client = aioxmpp.PresenceManagedClient(my_jid, security) presence = client.summon(aioxmpp.presence.Service) roster = client.summon(aioxmpp.roster.Service) class RosterItemAndCommandCompleter(Completer): def get_completions(self, document, complete_event): text = document.text if not text or " " in text or ":" in text: return if text[0] == "/": part = text[1:] for command in ("roster", "name", "add", "del", "help", "quit"): if command.startswith(part): yield Completion(command + " ", start_position=-len(part), display=command) elif roster is not None: for item in roster.items.values(): if item.name.startswith(text): yield Completion(item.name + ": ", start_position=-len(text), display=item.name) completer = RosterItemAndCommandCompleter() history = InMemoryHistory() next_recipient = None def get_prompt_tokens(_): return ((Token.Prompt, "%s> " % (next_recipient or "")),) cli_app = create_prompt_application(get_prompt_tokens=get_prompt_tokens, completer=completer, reserve_space_for_menu=0, history=history, get_title=lambda: "xc") cli_loop = create_asyncio_eventloop() cli = CommandLineInterface(application=cli_app, eventloop=cli_loop) above_prompt = cli.stdout_proxy() def name_for_jid(jid): try: return roster.items[jid].name except: return str(jid) def peer_available(jid, presence): above_prompt.write("%s is now online\n" % name_for_jid(jid.bare())) presence.on_available.connect(peer_available) def peer_unavailable(jid, presence): above_prompt.write("%s is now offline\n" % name_for_jid(jid.bare())) presence.on_unavailable.connect(peer_unavailable) def message_received(msg): content = " ".join(msg.body.values()) if content: above_prompt.write("%s: %s\n" % (name_for_jid(msg.from_.bare()), content)) client.stream.register_message_callback("chat", None, message_received) try: async with client.connected() as stream: while True: try: document = await cli.run_async() except (KeyboardInterrupt, EOFError): break line = document.text if line.startswith("/"): try: command, *args = line[1:].split(" ") if command == "roster": rows = [(item.name, str(jid), item.subscription) for jid, item in roster.items.items() if jid != my_jid] widths = [max(len(row[i] or "") for row in rows) for i in range(len(rows[0] or ""))] for row in rows: above_prompt.write(" ".join((cell or "").ljust(widths[i]) for i, cell in enumerate(row)) + "\n") elif command == "name": try: jid = args[0] name = args[1] except IndexError: above_prompt.write("usage: /name JID NAME\n") else: jid = aioxmpp.JID.fromstr(jid) await roster.set_entry(jid, name=name) elif command == "add": try: jid = args[0] except IndexError: above_prompt.write("usage: /add JID\n") else: jid = aioxmpp.JID.fromstr(jid) await roster.set_entry(jid) roster.subscribe(jid) roster.approve(jid) elif command == "del": try: jid = args[0] except IndexError: above_prompt.write("usage: /del JID\n") else: jid = aioxmpp.JID.fromstr(jid) await roster.remove_entry(jid) elif command == "quit": break elif command == "help": above_prompt.write("NAME: MESSAGE send MESSAGE to NAME\n" "MESSAGE send MESSAGE to the last correspondent\n" "/roster print the roster\n" "/add JID add JID to the roster\n" "/del JID remove JID from the roster\n" "/name JID NAME set the name of JID to NAME\n" "/quit disconnect and then quit (also ctrl-d, ctrl-c)\n" "/help this help\n") else: above_prompt.write("unrecognised command\n") except Exception as e: above_prompt.write("exception handling command: %s\n" % e) else: try: try: recipient, message = line.split(": ", 1) except ValueError: if next_recipient is not None: recipient = next_recipient message = line else: above_prompt.write("recipient: message\n") continue jid_for_name = lambda r: next((jid for jid, item in roster.items.items() if item.name == r), None) jid = jid_for_name(recipient) if jid is None: if next_recipient is not None and recipient != next_recipient: recipient = next_recipient jid = jid_for_name(recipient) message = line if jid is None: above_prompt.write("unknown recipient: %s\n" % recipient) continue msg = aioxmpp.Message(to=jid, type_="chat") msg.body[None] = message await stream.send_and_wait_for_sent(msg) next_recipient = recipient except Exception as e: above_prompt.write("exception sending message: %s\n" % e) print("Disconnecting…") client.stop() except Exception as e: print("Failed to connect: %s" % e)