{ "info": { "author": "Grigory Statsenko", "author_email": "UNKNOWN", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "PyMander\r\n========\r\n\r\nIntroduction\r\n------------\r\n\r\nPyMander (short for Python Commander) is a library for writing interactive command-line interface (CLI)\r\napplications in Python.\r\n\r\nQuick Start\r\n-----------\r\n\r\nLet's say, we need a CLI app that has two commands: ``date`` and ``time`` that print the current date\r\nand time respectively. Then you would do something like this:\r\n\r\n.. code-block:: python\r\n\r\n import time\r\n from pymander.handlers import LineHandler\r\n from pymander.exceptions import CantParseLine\r\n from pymander.shortcuts import run_with_handler\r\n \r\n class DatetimeLineHandler(LineHandler):\r\n def try_execute(self, line):\r\n if line.strip() == 'time':\r\n self.context.write(time.strftime('%H:%M:%S\\n'))\r\n elif line.strip() == 'date':\r\n self.context.write(time.strftime('%Y.%d.%d\\n'))\r\n else:\r\n raise CantParseLine(line)\r\n \r\n \r\n run_with_handler(DatetimeLineHandler())\r\n\r\nAnd you'll get... (just type ``exit`` to exit the loop)\r\n\r\n::\r\n\r\n >>> date\r\n 2016.14.14\r\n >>> time \r\n 01:00:00\r\n >>> exit \r\n Bye!\r\n\r\n\r\nLet's spice things up and add some time travel functionality to your app. Adding a lot of commands\r\nto the same function as if-blocks is not a very good idea, besides you might want to keep warping of the Universe\r\nseparate from the code that just shows the date and time, so go ahead and create a new handler:\r\n\r\n.. code-block:: python\r\n\r\n import re\r\n\r\n class TimeTravelLineHandler(LineHandler):\r\n def try_execute(self, line):\r\n cmd_match = re.match('go to date (?P.*?)\\s*$', line)\r\n if cmd_match:\r\n new_date = line.split(' ', 2)[-1]\r\n self.context.write('Traveling to date: {0}\\n'.format(cmd_match.group('new_date')))\r\n else:\r\n raise CantParseLine(line)\r\n\r\nAt this point we have a problem: how do we use the two handlers in our app simultaneously?\r\n\r\nCommand contexts are a way of combining several handlers in a single scope so that they can work together.\r\nHaving said that, let's run it using a ``StandardPrompt`` command context:\r\n\r\n.. code-block:: python\r\n\r\n from pymander.contexts import StandardPrompt\r\n from pymander.shortcuts import run_with_context\r\n \r\n run_with_context(\r\n StandardPrompt([\r\n DatetimeLineHandler(),\r\n TimeTravelLineHandler()\r\n ])\r\n )\r\n\r\nAnd back to the future we go!\r\n\r\n::\r\n\r\n >>> date\r\n 2016.14.14\r\n >>> go to date October 10 2058\r\n Traveling to date: October 10 2058\r\n\r\n\r\nIt's worth mentioning that ``run_with_handler(handler)`` is basically a shortcut\r\nfor ``run_with_context(StandardPrompt([handler]))``.\r\n\r\n``StandardPrompt`` is a simple command context that includes the following features:\r\n\r\n- prints the ``\">>> \"`` when prompting for a new command\r\n- writes \"Invalid command: ...\" when it cannot recognize a command\r\n- adds the ``EchoLineHandler`` and ``ExitLineHandler`` handlers, which implement the ``echo`` and ``exit`` commands, which do pretty much what you expect them to do\r\n\r\n\r\nMore Examples\r\n-------------\r\n\r\nMoving on to more complicated examples...\r\n\r\n****\r\n\r\n**Using regular expresssions (RegexLineHandler)**\r\n\r\nExample:\r\n\r\n.. code-block:: python\r\n\r\n from pymander.decorators import bind_command\r\n\r\n class BerryLineHandler(RegexLineHandler):\r\n @bind_command(r'pick a (?P\\w+)')\r\n def pick_berry(self, berry_kind):\r\n self.context.write('Picked a {0}\\n'.format(berry_kind))\r\n\r\n @bind_command(r'make (?P\\w+) jam')\r\n def make_jam(self, berry_kind):\r\n self.context.write('Made some {0} jam\\n'.format(berry_kind))\r\n\r\nOutput:\r\n\r\n::\r\n\r\n >>> pick a strawberry\r\n Picked a strawberry\r\n >>> make blueberry jam\r\n Made some blueberry jam\r\n\r\n\r\n****\r\n\r\n**Using argparse (ArgparseLineHandler)**\r\n\r\nExample:\r\n\r\n.. code-block:: python\r\n\r\n from pymander.decorators import bind_command\r\n\r\n class GameLineHandler(ArgparseLineHandler):\r\n @bind_command('play', [\r\n ['game', {'type': str, 'default': 'nothing'}],\r\n ['--well', {'action': 'store_true'}],\r\n ])\r\n def play(self, game, well):\r\n self.context.write('I play {0}{1}\\n'.format(game, ' very well' if well else ''))\r\n\r\n @bind_command('win')\r\n def win(self):\r\n self.context.write('I just won!\\n')\r\n\r\n\r\nOutput:\r\n\r\n::\r\n\r\n >>> play chess --well\r\n I play chess very well\r\n >>> play monopoly\r\n I play monopoly\r\n >>> win\r\n I just won!\r\n\r\n\r\n****\r\n\r\n**Combining argparse and regexes using PrebuiltCommandContext**\r\n\r\nSometimes you might find it useful to be able to use both approaches together or be able to switch\r\nfrom one to another without making a mess of a whole bunch of handlers.\r\n\r\n``PrebuiltCommandContext`` allows you to use decorators to assign its own methods\r\nas either argparse or regex commands in a single (command context) class without having to define the handlers yourself:\r\n\r\n.. code-block:: python\r\n\r\n from pymander.contexts import PrebuiltCommandContext, StandardPrompt\r\n from pymander.shortcuts import run_with_context\r\n from pymander.decorators import bind_argparse, bind_regex\r\n\r\n class SaladContext(PrebuiltCommandContext, StandardPrompt):\r\n @bind_regex(r'(?Peat|cook) caesar')\r\n def caesar_salad(self, do_what):\r\n self.write('{0}ing caesar salad...\\n'.format(do_what.capitalize()))\r\n\r\n @bind_argparse('buy', [\r\n 'kind_of_salad',\r\n ['--price', '-p', {'default': None}]\r\n ])\r\n def buy_salad(self, kind_of_salad, price):\r\n self.write('Buying {0} salad{1}...\\n'.format(\r\n kind_of_salad, ' for {0}'.format(price) if price else '')\r\n )\r\n \r\n run_with_context(SaladContext())\r\n\r\n\r\nExample:\r\n\r\n::\r\n\r\n >>> cook caesar\r\n Cooking caesar salad...\r\n >>> buy greek\r\n Buying greek salad...\r\n >>> buy russian --price $5\r\n Buying russian salad for $5...\r\n\r\n\r\nThe ``PrebuiltCommandContext`` class can be used with three decorators for assigning methods to specific handlers:\r\n\r\n- ``bind_exact(command)`` binds to ``ExactLineHandler`` (matches the line exactly to the specified string, e.g. the ``exit`` command)\r\n- ``bind_argparse(command, options)`` binds to ``ArgparseLineHandler`` (uses argparse to evaluate the line)\r\n- ``bind_regex(regex)`` binds to ``RegexLineHandler`` (matches the line to regular expressions)\r\n\r\nand one generic decorator:\r\n\r\n- ``bind_to_handler(handler_class, *bind_args, **bind_kwargs)``\r\n\r\nbinds to any given LineHandler subclass. The handler class can then access its autogenerated methods\r\nvia the ``self.command_methods`` attribute:\r\n\r\n.. code-block:: python\r\n\r\n class MyLineHandler(LineHandler):\r\n def try_execute(self, line):\r\n for command_info in self.command_methods:\r\n # where: command_info = {\"method\": , \"args\": , \"kwargs\": }\r\n # your logic goes here:\r\n # determine whether matches the and options)\r\n # and call the callable if it does\r\n pass\r\n\r\n # if no suitable match was found:\r\n raise CantParseLine\r\n\r\n\r\nAnd then use it like this:\r\n\r\n.. code-block:: python\r\n\r\n class MyPrebuiltContext(PrebuiltCommandContext, StandardPrompt):\r\n @bind_to_handler(MyLineHandler, 'some', 'arguments')\r\n def do_whatever(self, *your_method_args):\r\n self.write('Whatever, bro\\n')\r\n\r\n\r\nAt this point you might be wondering, why we always also use ``StandardPrompt`` when inheriting\r\nfrom ``PrebuiltCommandContext``. That's because ``PrebuiltCommandContext`` is an abstract class and does not\r\nimplement some of the required ``CommandContext`` methods. So this is where I'd normally send you\r\nto the full documentation of the project, but it's not finished yet, so, for now, you can just browse\r\nthe source code of the examples and the ``pymander`` package itself :)\r\n\r\nUsing Nested Contexts\r\n---------------------\r\n\r\nAn obvious extension would be the ability to enter a new context on some commands and then exit them\r\n(multi-step commands, entering and exiting a file editor, etc.).\r\nAll you have to do to use this is return an instance of a new ``CommandContext`` from your command,\r\nand you're in! Just don't forget to supply this context with an ``exit``, or you'll be stuck in there forever.\r\n\r\nSee ``DeeperLineHandler`` in the `simple `_ example.\r\n\r\n\r\nUsing Multiline Commands (text input)\r\n-------------------------------------\r\n\r\nCheck out the `multi `_ and `fswalk `_ examples.\r\n\r\n\r\nMajor TODOs\r\n-----------\r\n\r\nHere I'll be listing some of the major fetures that are not yet implemented, but are crucial to the library's usability.\r\n\r\n#. an easy to use help mechanism. It should be able to list possible commands and how they should be used (like in argparse)\r\n#. read input by character instead of by line to handle special characters (`Esc`, `Ctrl`, arrows keys, etc.). This might also mean using OS-specific adapters for the console", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/altvod/pymander", "keywords": "interactive shell argparse command console", "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "pymander", "package_url": "https://pypi.org/project/pymander/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/pymander/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/altvod/pymander" }, "release_url": "https://pypi.org/project/pymander/0.2.2/", "requires_dist": null, "requires_python": null, "summary": "An interactive console application library for Python", "version": "0.2.2" }, "last_serial": 2334591, "releases": { "0.2.0": [], "0.2.1": [ { "comment_text": "", "digests": { "md5": "76784ddd5ac9a06361feb510d540915b", "sha256": "973a94bc9fd43c5ff022792d28c38f946af0cb20790cb2bc2e53046b629c9a39" }, "downloads": -1, "filename": "pymander-0.2.1.tar.gz", "has_sig": false, "md5_digest": "76784ddd5ac9a06361feb510d540915b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12059, "upload_time": "2016-09-10T00:44:14", "url": "https://files.pythonhosted.org/packages/1a/6f/bb6c34c7dfa81b87f4ce156eab52a8c6b422f8b1260cc196bfa2c5f35358/pymander-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "fe979951ab280d0d04b5b5f4c716a2ad", "sha256": "1c2201eab0f4ef57d6b8f9864713bceae5afb0642d7d17d85926fe271750dd9b" }, "downloads": -1, "filename": "pymander-0.2.2.tar.gz", "has_sig": false, "md5_digest": "fe979951ab280d0d04b5b5f4c716a2ad", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11903, "upload_time": "2016-09-10T00:59:53", "url": "https://files.pythonhosted.org/packages/2f/05/80d6c874f1b13d470633aea36321f3d86795aad550bae3d2882b0a055fc5/pymander-0.2.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "fe979951ab280d0d04b5b5f4c716a2ad", "sha256": "1c2201eab0f4ef57d6b8f9864713bceae5afb0642d7d17d85926fe271750dd9b" }, "downloads": -1, "filename": "pymander-0.2.2.tar.gz", "has_sig": false, "md5_digest": "fe979951ab280d0d04b5b5f4c716a2ad", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11903, "upload_time": "2016-09-10T00:59:53", "url": "https://files.pythonhosted.org/packages/2f/05/80d6c874f1b13d470633aea36321f3d86795aad550bae3d2882b0a055fc5/pymander-0.2.2.tar.gz" } ] }