diff --git a/.gitignore b/.gitignore index 1e53c39..a9b7303 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ build/ develop-eggs/ dist/ eggs/ +.eggs/ lib/ lib64/ parts/ @@ -29,6 +30,7 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ +.pytest_cache/ .tox/ .coverage .cache diff --git a/.travis.yml b/.travis.yml index 402e6b7..85c5645 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,20 +2,18 @@ language: python python: - "2.7" - - "3.4" - "3.5" - "3.6" - "3.7" - "3.8" + - "3.9-dev" install: + - pip install -r requirements.txt + - pip install -r test/requirements.txt + - pip install -r docs/requirements.txt - pip install coverage - pip install coveralls - - pip install future - - pip install mock - - pip install configparser - - pip install pytest - - pip install sphinx script: - git fetch diff --git a/README.md b/README.md index e3e78f8..34b87d8 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,18 @@ The latest version can be installed from PyPI using `pip`: sudo pip install prompty ``` -You then need to insert a line at the end of your `.bashrc` file so that prompty is called from the `PS1` environment variable: +It is a good idea to test that prompty is working correctly before continuing. Run the `prompty` command on its own and ensure that there are no errors: ```bash -prompty -b >> ~/.bashrc +prompty +``` + +If all has gone well, you should see some colourful output. (If not, see the tip section below for some ideas). + +In order for for prompty to be integrated into your bash prompt, you need to insert a line at the end of your `.bashrc` file so that it is called from your `PS1` environment variable: + +```bash +prompty gen-bashrc >> ~/.bashrc ``` Now re-source your updated `.bashrc` file: @@ -31,11 +39,18 @@ source ~/.bashrc ``` (alternatively you can restart your shell session) +You should now see the default prompty prompt. -> **Tip:** If you get an error like "`bash: prompty: command not found`", it is probably because you installed it locally, as a non-root user (without `sudo`). This is fine, but you will need to call the prompty executable from its local path: +> **Tip:** If you get an error like "`prompty: command not found`", it is probably because you installed it locally as a non-root user (without `sudo`). This is fine, but you will need to call the prompty executable from its local path. The previous commands can be replaced with: > -> `~/.local/bin/prompty -b >> ~/.bashrc` - +> `# Test that prompty works` +> `~/.local/bin/prompty` +> +> `# Update .bashrc file` +> `~/.local/bin/prompty bashrc >> ~/.bashrc` +> +> `# Reload .bashrc` +> `source ~/.bashrc` # Configuration diff --git a/bin/prompty b/bin/prompty index cdb9fe0..0bec0c7 100755 --- a/bin/prompty +++ b/bin/prompty @@ -3,138 +3,25 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import datetime START = datetime.datetime.now() # noqa # Import external modules import sys -import os -import getopt -import codecs +import os.path # Add base directory to path so that it can find the prompty package sys.path[0:0] = [os.path.join(os.path.dirname(__file__), "..")] # noqa -import prompty +import prompty.cli +prompty.cli.START = START -# Overload sys.stdout to support unicode -UTF8Writer = codecs.getwriter('utf-8') -sys.stdout = UTF8Writer(sys.stdout) -USAGE = "Usage: %s [options]" % sys.argv[0] + """ -Options: -h, --help Display this help message and exit -""" +def main(): + prompty.cli.cli() -def usage(msg=''): - """Print usage information to stderr. - - @param msg: An optional message that will be displayed before the usage - @return: None - """ - if msg: - print(msg, file=sys.stderr) - print(USAGE, file=sys.stderr) - - -def main(argv=None): - """Main function. This is the entry point for the program and is run when - the script is executed stand-alone (i.e. not included as a module - - @param argv: A list of argumets that can over-rule the command line arguments. - @return: Error status - @rtype: int - """ - - # Use the command line (system) arguments if none were passed to main - if argv is None: - argv = sys.argv - - # Parse command line options - try: - opts, args = getopt.getopt(argv[1:], "hbcdpw:v", [ - "help", "bash", "colours", "debug", "palette", "working-dir=", "version" - ]) - except getopt.error as msg: - usage(msg.msg) - return 1 - - # Defaults - debug = False - workingDir = None - - # Act upon options - for option, arg in opts: - if option in ("-h", "--help"): - usage() - return 0 - - if option in ("-b", "--bashrc"): - abs_path = os.path.abspath(sys.argv[0]) - print("export PS1=\"\\$(%s \\$?)\"" % abs_path) - return 0 - - if option in ("-c", "--colours"): - c = prompty.colours.Colours(prompty.functionContainer.FunctionContainer()) - for style in c.STYLES: - for colour in c.COLOURS: - print("%s%s : %s%s" % (c.startColour(colour, style=style, _wrap=False), - style[c.NAME_KEY], - colour[c.NAME_KEY], - c.stopColour(_wrap=False))) - return 0 - - if option in ("-d", "--debug"): - debug = True - - if option in ("-p", "--palette"): - c = prompty.colours.Colours(prompty.functionContainer.FunctionContainer()) - for colour in c.PALETTE: - print("%s%s%s" % ( - c.startColour( - fgcolour=colour[c.FG_KEY], - bgcolour=colour[c.BG_KEY], - style=colour[c.STYLE_KEY], - _wrap=False - ), - colour[c.NAME_KEY], - c.stopColour(_wrap=False)) - ) - return 0 - - if option in ("-w", "--working-dir"): - workingDir = arg - - if option in ("-v", "--version"): - print(prompty.__version__) - return 0 - - if len(args) < 1: - usage("Not enough arguments") - return 1 - - exitStatus = int(args[0]) - - s = prompty.status.Status(exitStatus, workingDir) - - p = prompty.prompt.Prompt(s) - - prompt = p.getPrompt() - - if debug: - elapsed = datetime.datetime.now() - START - sys.stdout.write("%d\n" % (elapsed.total_seconds()*1000)) - - sys.stdout.write(prompt) - - return exitStatus - - -# ---------------------------------------------------------------------------# -# End of functions # -# ---------------------------------------------------------------------------# # Run main if this file is not imported as a module if __name__ == "__main__": - sys.exit(main()) + main() diff --git a/docs/conf.py b/docs/conf.py index 11294e6..8bef874 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = u'' # The full version, including alpha/beta/rc tags -release = u'0.3.0' +release = u'0.4.rc1' # -- General configuration --------------------------------------------------- diff --git a/prompty/__init__.py b/prompty/__init__.py index 74c3875..4e97189 100644 --- a/prompty/__init__.py +++ b/prompty/__init__.py @@ -2,7 +2,8 @@ # vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab: # Must comply with http://legacy.python.org/dev/peps/pep-0440/#version-scheme -__version__ = "0.3.0" +__version__ = "0.4.rc2" + from . import prompt from . import functions @@ -16,3 +17,4 @@ from . import config from . import git from . import svn +from . import cli diff --git a/prompty/cli.py b/prompty/cli.py new file mode 100644 index 0000000..2f02905 --- /dev/null +++ b/prompty/cli.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +# vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab: +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from builtins import chr +from future.utils import iteritems + +# Import external modules +import datetime +import sys +import os +import getopt +import codecs +import click +from collections import OrderedDict + +import prompty + +# Should be overridden by bin/prompty +START = 0 + + +class RunConfig(object): + options = [ + # All options should have default=None + # Set the real defaults in __init__() below + click.option("--exit-status", "-e", default=None, type=int, help="The exit status of the last command. Also " + "implies -W -n -r."), + click.option("--wrap", "-W", default=None, is_flag=True, help="Wrap non-printing characters with \\001 and " + "\\002 to maintain correct line length."), + click.option("--no-nl", "-n", default=None, is_flag=True, help="Do not output the trailing newline."), + click.option("--raw", "-r", default=None, is_flag=True, help="Print raw output (no titles or boxes)."), + click.option("--working-dir", "-w", default=None, help="The working directory (default is cwd)."), + click.option("--debug", "-d", default=None, is_flag=True, help="Run in debug mode."), + click.option("--version", "-v", default=None, is_flag=True, help="Print the version."), + ] + + def __init__(self): + self.exit_status = None + self.working_dir = None + self.version = False + self.debug = False + self.no_nl = False + self.wrap = False + self.raw = False + + def update(self, kwargs): + for key, value in kwargs.items(): + if value is not None: + setattr(self, key, value) + + @staticmethod + def add_options(func): + for option in reversed(RunConfig.options): + func = option(func) + return func + + +RunConfig.pass_config = staticmethod( + click.make_pass_decorator(RunConfig, ensure=True) +) + + +@click.group( + invoke_without_command=True, + context_settings={"help_option_names": ["-h", "--help"]} +) +@RunConfig.add_options +@RunConfig.pass_config +@click.pass_context +def cli(ctx, config, **kwargs): + """ + Promty is a bash prompt markup language. Use one of the COMMANDS below: + """ + config.update(kwargs) + + if ctx.invoked_subcommand is None: + # Run default command (run) + return ctx.invoke(run) + + +@cli.command() +@click.argument("scripts", nargs=-1) +@click.option("--all", "-a", is_flag=True, help="Run all installed prompty scripts.") +@RunConfig.add_options +@RunConfig.pass_config +def run(config, all, scripts, **kwargs): + """ + Execute a prompty script (default). + + `run` is the default command, if no other is given. + + If multiple SCRIPTS are provided, each one will be executed. + SCRIPTS can either be absolute path names or short names of + those installed in the ~/.local/share/prompty/ directory. + """ + config.update(kwargs) + + if config.version: + click.echo(prompty.__version__) + return + + if config.exit_status is None: + # Probably called from CLI + config.exit_status = 0 + else: + # Probably called from PS1 + config.wrap = True + config.no_nl = True + config.raw = True + + status = prompty.status.Status(config.exit_status, config.working_dir) + status.wrap = config.wrap + + if all: + # Print installed all scripts + prompt = prompty.prompt.Prompt(status) + scripts = [os.path.splitext(os.path.basename(s))[0] for s in prompt.config.get_prompt_files()] + + prompts = OrderedDict() + for script in scripts: + prompt = prompty.prompt.Prompt(status) + script_filepath = prompt.config.get_prompt_file_path(script) + if not script_filepath: + raise click.BadParameter("Prompty file '{}' not found".format(script)) + prompt.config.load_prompt_file(script_filepath) + prompts[script] = prompt + + if not prompts: + # Add default + prompt = prompty.prompt.Prompt(status) + prompts["{} (current)".format(os.path.basename(prompt.config.prompt_file))] = prompt + + for file, prompt in iteritems(prompts): + if config.raw: + click.echo(prompt.get_prompt(), nl=(not config.no_nl), color=True) + else: + prompt.status.window.column -= 2 + print_box(file, prompt.get_prompt()) + prompt.status.window.column += 2 + + if config.debug and START: + elapsed = datetime.datetime.now() - START + sys.stdout.write("Run time: %dms\n" % (elapsed.total_seconds()*1000)) + + sys.exit(config.exit_status) + + +@cli.command() +def gen_bashrc(): + """ + Print a .bashrc invocation line. + """ + abs_path = os.path.abspath(sys.argv[0]) + click.echo("export PS1=\"\\$(%s -e \\$?)\"" % abs_path) + + +@cli.command() +def colours(): + """ + Print available prompty colours. + """ + c = prompty.colours.Colours(prompty.functionContainer.FunctionContainer()) + c.status.wrap = False + for style in c.STYLES: + for colour in c.COLOURS: + click.echo("%s%s : %s%s" % ( + c.startcolour(colour, style=style), + style[c.NAME_KEY], + colour[c.NAME_KEY], + c.stopcolour()) + ) + + +@cli.command() +def palette(): + """ + Print available prompty colour palette. + """ + c = prompty.colours.Colours(prompty.functionContainer.FunctionContainer()) + c.status.wrap = False + for colour in c.PALETTE: + click.echo("%s%s%s" % ( + c.startcolour( + fgcolour=colour[c.FG_KEY], + bgcolour=colour[c.BG_KEY], + style=colour[c.STYLE_KEY] + ), + colour[c.NAME_KEY], + c.stopcolour()) + ) + + +@cli.command() +@click.option("--raw", "-r", is_flag=True, help="Print raw filenames") +def ls(raw): + """ + Show the list of prompty scripts. + """ + status = prompty.status.Status() + status.wrap = False + prompt = prompty.prompt.Prompt(status) + current_prompt_file = prompt.config.prompt_file + + if not raw: + click.echo("Prompty files:") + + for filepath in prompt.config.get_prompt_files(): + base = os.path.basename(filepath) + if raw: + leader = "" + else: + leader = "[*] " if current_prompt_file == filepath else "[ ] " + click.echo(leader + base) + + +@cli.command() +@click.argument("file") +def use(file): + """ + Change the current prompty file + """ + status = prompty.status.Status() + prompt = prompty.prompt.Prompt(status) + + file_with_ext = file+".prompty" + files = [os.path.basename(f) for f in prompt.config.get_prompt_files()] + if file_with_ext not in files: + raise click.BadParameter("Prompty file '{}' not found".format(file_with_ext)) + + status = prompty.status.Status() + config = prompty.config.Config() + config.load(status.user_dir.get_config_file()) + config.config_parser.set('prompt', 'prompt_file', file_with_ext) + config.save() + + +def print_box(title, contents): + cols = prompty.status.Status().window.column + + # Add fake cursor to end of prompt + contents += chr(9608) + + if not cols: + # Print simple + click.echo(title) + click.echo(chr(0x2550)*len(title)) + click.echo(contents, color=True) + click.echo() + return + + # Print with boxes + + # Top bar + click.echo(chr(0x2554), nl=False) + click.echo(chr(0x2550)*(len(title)+2), nl=False) + click.echo(chr(0x2566), nl=False) + click.echo(chr(0x2550)*(cols-(len(title)+5)), nl=False) + click.echo(chr(0x2557), nl=False) + click.echo() + + # Title line + click.echo(chr(0x2551), nl=False) + click.echo(" "+title+" ", nl=False) + click.echo(chr(0x2551), nl=False) + click.echo(" "*(cols-(len(title)+5)), nl=False) + click.echo(chr(0x2551), nl=False) + click.echo() + + # Below title + click.echo(chr(0x2560), nl=False) + click.echo(chr(0x2550)*(len(title)+2), nl=False) + click.echo(chr(0x255d), nl=False) + click.echo(" "*(cols-(len(title)+5)), nl=False) + click.echo(chr(0x2551), nl=False) + click.echo() + + # Contents line + contents = add_border(contents, chr(0x2551), cols) + click.echo(contents, color=True, nl=False) + click.echo() + + # Below contents + click.echo(chr(0x255a), nl=False) + click.echo(chr(0x2550)*(cols-2), nl=False) + click.echo(chr(0x255d), nl=False) + click.echo() + + +def add_border(contents, border_chars, cols): + new_lines = [] + for line in contents.splitlines(): + rpad = " "*(cols-(len(escape_ansi(line))+2)) + new_lines.append(border_chars + line + rpad + border_chars) + return "\n".join(new_lines) + + +def escape_ansi(line): + import re + ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]') + return ansi_escape.sub('', line) diff --git a/prompty/colours.py b/prompty/colours.py index d913e34..5f2429a 100644 --- a/prompty/colours.py +++ b/prompty/colours.py @@ -3,8 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals -from builtins import str # Import external modules import re @@ -64,23 +62,19 @@ class Colours(functionBase.PromptyFunctions): END_CODE = "m" def __init__(self, container): - self._populateFunctions() + self._populate_functions() super(Colours, self).__init__(container) - def _encode(self, code, wrap=True): + def _encode(self, code): """ Add the bash escape codes for colours i.e.: \\e[CODEm - - :param wrap: Wrap the code in additional characters - to signify non-printing characters are - contained """ if code == "": return "" s = self.ESCAPE_CHAR + str(code) + self.END_CODE - if wrap: + if self.status.wrap: s = self.NOCOUNT_START + s + self.NOCOUNT_END return s @@ -100,7 +94,7 @@ def _encode(self, code, wrap=True): INFO3 = {NAME_KEY: 'info3', FG_KEY: LMAGENTA, BG_KEY: None, STYLE_KEY: None} PALETTE = [DIM1, DIM2, DIM3, BRIGHT, ERROR, WARNING, INFO1, INFO2, INFO3] - def _setPalette(self, identifier, fgcolour=None, bgcolour=None, style=None): + def _set_palette(self, identifier, fgcolour=None, bgcolour=None, style=None): if identifier is None: return @@ -123,7 +117,7 @@ def _setPalette(self, identifier, fgcolour=None, bgcolour=None, style=None): if style: pal[self.STYLE_KEY] = style - def _getPaletteColourCode(self, identifier): + def _get_palette_colour_code(self, identifier): """ Palette colour codes can be any of the following: pal1, pal2, etc. @@ -133,14 +127,14 @@ def _getPaletteColourCode(self, identifier): for pal in self.PALETTE: if identifier == pal[self.NAME_KEY]: - style = str(self._getStyleCode(pal[self.STYLE_KEY])) - fg = str(self._getColourCode(pal[self.FG_KEY], self.FOREGROUND)) - bg = str(self._getColourCode(pal[self.BG_KEY], self.BACKGROUND)) + style = str(self._get_style_code(pal[self.STYLE_KEY])) + fg = str(self._get_colour_code(pal[self.FG_KEY], self.FOREGROUND)) + bg = str(self._get_colour_code(pal[self.BG_KEY], self.BACKGROUND)) return ";".join([x for x in [style, fg, bg] if x]) raise ValueError - def _get4bitColourCode(self, identifier, area=FOREGROUND): + def _get_4bit_colour_code(self, identifier, area=FOREGROUND): """ 4-bit colour codes can be any of the following: r, red, lr, lightred, etc. @@ -179,7 +173,7 @@ def _get4bitColourCode(self, identifier, area=FOREGROUND): 0x5c, 0x63, 0x6e, 0x7b, 0x85, 0x8f, 0x99, 0xa3, 0xad, 0xb7, 0xc1, 0xcb, 0xd5, 0xdf, 0xe9, 0xf7, 0xff] - def _get8bitColourCode(self, identifier, area=FOREGROUND): + def _get_8bit_colour_code(self, identifier, area=FOREGROUND): """ 8-bit colour codes can be any of the following: 0, 5, 126, 255, #0f0, #fff, #a3e, etc. @@ -245,7 +239,7 @@ def _get8bitColourCode(self, identifier, area=FOREGROUND): raise ValueError - def _get24bitColourCode(self, identifier, area=FOREGROUND): + def _get_24bit_colour_code(self, identifier, area=FOREGROUND): """ 24-bit colour codes can be any of the following: #000000, #aaf4d3, 0,255,0, 255,255,255 @@ -285,34 +279,34 @@ def _get24bitColourCode(self, identifier, area=FOREGROUND): raise ValueError - def _getColourCode(self, identifier, area=FOREGROUND): + def _get_colour_code(self, identifier, area=FOREGROUND): try: - colourCode = self._getPaletteColourCode(identifier) - return colourCode + colour_code = self._get_palette_colour_code(identifier) + return colour_code except ValueError: pass try: - colourCode = self._get4bitColourCode(identifier, area) - return colourCode + colour_code = self._get_4bit_colour_code(identifier, area) + return colour_code except ValueError: pass try: - colourCode = self._get8bitColourCode(identifier, area) - return colourCode + colour_code = self._get_8bit_colour_code(identifier, area) + return colour_code except ValueError: pass try: - colourCode = self._get24bitColourCode(identifier, area) - return colourCode + colour_code = self._get_24bit_colour_code(identifier, area) + return colour_code except ValueError: pass raise ValueError("No such colour %s" % str(identifier)) - def _getStyleCode(self, identifier): + def _get_style_code(self, identifier): if identifier is None: return "" @@ -333,16 +327,16 @@ def _getStyleCode(self, identifier): raise KeyError("No such style %s" % str(identifier)) @staticmethod - def _colourFuncFactory(clr): + def _colour_func_factory(clr): def fgfunc(slf, literal, style=None): - return slf.startColour(fgcolour=clr, style=style) + \ + return slf.startcolour(fgcolour=clr, style=style) + \ literal + \ - slf.stopColour() + slf.stopcolour() def bgfunc(slf, literal, style=None): - return slf.startColour(bgcolour=clr, style=style) + \ + return slf.startcolour(bgcolour=clr, style=style) + \ literal + \ - slf.stopColour() + slf.stopcolour() fgfunc.__doc__ = """ Set {} foreground colour for ``literal``. @@ -359,11 +353,11 @@ def bgfunc(slf, literal, style=None): return fgfunc, bgfunc @staticmethod - def _styleFuncFactory(style): + def _style_func_factory(style): def func(slf, literal): - return slf.startColour(style=style) + \ + return slf.startcolour(style=style) + \ literal + \ - slf.stopColour() + slf.stopcolour() func.__doc__ = """ Set the style to {} for ``literal``. @@ -372,11 +366,11 @@ def func(slf, literal): return func @staticmethod - def _paletteFuncFactory(pal): + def _palette_func_factory(pal): def func(slf, literal): - return slf.startColour(fgcolour=pal) + \ + return slf.startcolour(fgcolour=pal) + \ literal + \ - slf.stopColour() + slf.stopcolour() func.__doc__ = """ Set the pallet colour to {} for ``literal``. @@ -385,7 +379,7 @@ def func(slf, literal): return func @staticmethod - def _populateFunctions(): + def _populate_functions(): """ This will define functions for all 4-bit colours. The function definitions are of the form: @@ -396,23 +390,23 @@ def _populateFunctions(): """ for c in Colours.COLOURS: - colourName = c[Colours.NAME_KEY] - fgfunc, bgfunc = Colours._colourFuncFactory(colourName) - setattr(Colours, colourName, fgfunc) - setattr(Colours, colourName+"bg", bgfunc) + colour_name = c[Colours.NAME_KEY] + fgfunc, bgfunc = Colours._colour_func_factory(colour_name) + setattr(Colours, colour_name, fgfunc) + setattr(Colours, colour_name+"bg", bgfunc) for s in Colours.STYLES: - styleName = s[Colours.NAME_KEY] - func = Colours._styleFuncFactory(styleName) - setattr(Colours, styleName, func) + style_name = s[Colours.NAME_KEY] + func = Colours._style_func_factory(style_name) + setattr(Colours, style_name, func) for p in Colours.PALETTE: - paletteName = p[Colours.NAME_KEY] - func = Colours._paletteFuncFactory(paletteName) - setattr(Colours, paletteName, func) + palette_name = p[Colours.NAME_KEY] + func = Colours._palette_func_factory(palette_name) + setattr(Colours, palette_name, func) # ------------------------ # Public methods # ------------------------ - def startColour(self, fgcolour=None, bgcolour=None, style=None, _wrap=True): + def startcolour(self, fgcolour=None, bgcolour=None, style=None): """ Start a colour block. @@ -420,34 +414,30 @@ def startColour(self, fgcolour=None, bgcolour=None, style=None, _wrap=True): :param bgcolour: The background colour. :param style: The character style. """ - colourCode = "" + colour_code = "" if style: - colourCode += str(self._getStyleCode(style)) + colour_code += str(self._get_style_code(style)) if fgcolour: - if colourCode: - colourCode += ";" - colourCode += str(self._getColourCode(fgcolour, self.FOREGROUND)) + if colour_code: + colour_code += ";" + colour_code += str(self._get_colour_code(fgcolour, self.FOREGROUND)) if bgcolour: - if colourCode: - colourCode += ";" - colourCode += str(self._getColourCode(bgcolour, self.BACKGROUND)) + if colour_code: + colour_code += ";" + colour_code += str(self._get_colour_code(bgcolour, self.BACKGROUND)) - return self._encode(colourCode, wrap=_wrap) + return self._encode(colour_code) - def stopColour(self, _wrap=True): + def stopcolour(self): """ Stop a colour block. - - :param wrap: Wrap the code in additional characters - to signify non-printing characters are - contained """ - return self._encode(self.RESET_KEY, wrap=_wrap) + return self._encode(self.RESET_KEY) - def colour(self, literal, fgcolour=None, bgcolour=None, style=None, _wrap=True): + def colour(self, literal, fgcolour=None, bgcolour=None, style=None): """ Wrap the string ``literal`` in a colour block. The colour is stopped when ``literal`` ends. @@ -455,10 +445,10 @@ def colour(self, literal, fgcolour=None, bgcolour=None, style=None, _wrap=True): :param bgcolour: The background colour. :param style: The character style. """ - return self.startColour(fgcolour=fgcolour, bgcolour=bgcolour, style=style, _wrap=_wrap) + \ + return self.startcolour(fgcolour=fgcolour, bgcolour=bgcolour, style=style) + \ literal + \ - self.stopColour(_wrap=_wrap) + self.stopcolour() # Populate the functions in this module -Colours._populateFunctions() +Colours._populate_functions() diff --git a/prompty/compiler.py b/prompty/compiler.py index 4fe0905..4fefcb1 100644 --- a/prompty/compiler.py +++ b/prompty/compiler.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals from builtins import str from prompty import parser @@ -18,34 +17,42 @@ class Compiler(object): Literals are output verbatim, or passed into functions for processing. """ - def __init__(self, functionContainer): + def __init__(self, function_container): # Compiler requires a valid FunctionContainer in order # to execute functions - self.funcs = functionContainer + self.funcs = function_container self.parser = parser.Parser() - self.parsedStruct = [] + self.parsed_struct = [] - def compile(self, promptString): - """ Parse a given promptString. Add the resulting + def clear(self): + """ + Clear the compiled struct + """ + self.parsed_struct = [] + + def compile(self, prompt_string, clear=True): + """ Parse a given prompt_string. Add the resulting list of dictionary items to the internal buffer ready for executing. """ - self.parsedStruct.extend(self.parser.parse(promptString)) + if clear: + self.clear() + self.parsed_struct.extend(self.parser.parse(prompt_string)) def execute(self): """ Execute the internal buffer and return the output string. """ - return self._execute(self.parsedStruct) + return self._execute(self.parsed_struct) def _move(self, string): - self.funcs.status.pos.incFromString(string) + self.funcs.status.pos.inc_from_string(string) return string - def _execute(self, parsedStruct): - out = "" - for element in parsedStruct: + def _execute(self, parsed_struct): + out = u"" + for element in parsed_struct: if element['type'] == 'literal': # Literals go to the output verbatim out += self._move(element['value']) diff --git a/prompty/config.py b/prompty/config.py index 038fd42..5c468ca 100644 --- a/prompty/config.py +++ b/prompty/config.py @@ -3,34 +3,66 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import os +import glob from configparser import ConfigParser class Config(object): def __init__(self): - self.promptString = "" - self.configFile = None - self.configDir = None - self.configParser = ConfigParser() - self.promptFile = None + self.prompt_string = "" + self.config_file = None + self.config_dir = None + self.config_parser = ConfigParser() + self.prompt_file = None def load(self, filename): - self.configFile = filename - self.configDir = os.path.dirname(filename) + self.config_file = filename + self.config_dir = os.path.dirname(filename) # Read and parse the config file - self.configParser.read(filename) + self.config_parser.read(filename) - self.promptFile = os.path.join( - self.configDir, - self.configParser.get('prompt', 'prompt_file') - ) + self.prompt_file = self.get_prompt_file_path() + + self.load_prompt_file(self.get_prompt_file_path()) + + def get_prompt_file_path(self, name=None): + if name is None: + # Get name from config + name = self.config_parser.get('prompt', 'prompt_file') + + suggestion = name + if os.path.isfile(suggestion): + return suggestion + + suggestion = os.path.join(self.config_dir, name) + if os.path.isfile(suggestion): + return suggestion + + suggestion = os.path.join(self.config_dir, name+".prompty") + if os.path.isfile(suggestion): + return suggestion - self.loadPromptFile() + return None - def loadPromptFile(self): - with open(self.promptFile, "r") as pFile: - self.promptString = pFile.read() + def save(self): + with open(self.config_file, 'w') as f: + self.config_parser.write(f) + + def load_prompt_file(self, filename=None): + if filename is None: + filename = self.prompt_file + + with open(filename, "r") as f: + self.prompt_string = f.read() + + self.prompt_file = filename + + def get_prompt_files(self): + return sorted( + glob.glob( + os.path.join(self.config_dir, "*.prompty") + ) + ) diff --git a/prompty/functionBase.py b/prompty/functionBase.py index 5fa5de4..261a3e2 100644 --- a/prompty/functionBase.py +++ b/prompty/functionBase.py @@ -3,11 +3,15 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import types +class MockStatus(object): + def __init__(self): + self.wrap = True + + def getmembers(obj, predicate=None): """ ** Extracted from inspect module for optimisation purposes ** Return all members of an object as (name, value) pairs sorted by name. @@ -72,16 +76,16 @@ def __init__(self, container=None): if self.functions: self.status = self.functions.status else: - self.status = None + self.status = MockStatus() @staticmethod - def _isPromptyFunctionsSubClass(obj): + def _is_prompty_functions_subclass(obj): return isclass(obj) and issubclass(obj, PromptyFunctions) def register(self): for name, func in getmembers(self, ismethod): if name[0] != "_": - self.functions.addFunction(name, func) + self.functions.add_function(name, func) def call(self, func, *args, **kwargs): return self.functions._call(func, *args, **kwargs) diff --git a/prompty/functionContainer.py b/prompty/functionContainer.py index 198e6a7..04b76f1 100644 --- a/prompty/functionContainer.py +++ b/prompty/functionContainer.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals # Import external modules # import inspect # Removed for being slow to import @@ -25,13 +24,13 @@ def _call(self, *args, **kwargs): name = args[0] return self.functions[name](*args[1:], **kwargs) - def addFunction(self, name, func): + def add_function(self, name, func): self.functions[name] = func - def addFunctionsFromModule(self, module): + def add_functions_from_module(self, module): for _, cls in functionBase.getmembers( module, - functionBase.PromptyFunctions._isPromptyFunctionsSubClass): + functionBase.PromptyFunctions._is_prompty_functions_subclass): # Instantiate class obj = cls(self) # Store object so that it is not garbage collected @@ -39,10 +38,10 @@ def addFunctionsFromModule(self, module): # Register prompty functions obj.register() - def addFunctionsFromDir(self, directory): + def add_functions_from_dir(self, directory): for filename in glob.glob(os.path.join(directory, "*.py")): module = imp.load_source('user', filename) - self.addFunctionsFromModule(module) + self.add_functions_from_module(module) def __init__(self, status=None): if status is None: diff --git a/prompty/functions.py b/prompty/functions.py index 9445d49..7eb7425 100644 --- a/prompty/functions.py +++ b/prompty/functions.py @@ -3,8 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals -from builtins import str from builtins import chr # Import external modules @@ -140,19 +138,19 @@ def powerline(self, content, bg="blue", bg_next="default", fg="white", dir="righ """ out = "" if dir == "left": - out += self.call("startColour", bgcolour=bg_next) - out += self.call("startColour", fgcolour=bg) + out += self.call("startcolour", bgcolour=bg_next) + out += self.call("startcolour", fgcolour=bg) out += self.plleftarrowfill() - out += self.call("startColour", fgcolour=fg) - out += self.call("startColour", bgcolour=bg) + out += self.call("startcolour", fgcolour=fg) + out += self.call("startcolour", bgcolour=bg) out += " " out += content out += " " if dir == "right": - out += self.call("startColour", bgcolour=bg_next) - out += self.call("startColour", fgcolour=bg) + out += self.call("startcolour", bgcolour=bg_next) + out += self.call("startcolour", fgcolour=bg) out += self.plrightarrowfill() - out += self.call("stopColour") + out += self.call("stopcolour") return out def plbranch(self): @@ -276,7 +274,7 @@ def workingdir(self): Equivalent to the bash prompt escape sequence ``\\w``. """ - cwd = self.status.getWorkingDir() + cwd = self.status.get_working_dir() home = os.path.expanduser(r"~") return re.sub(r'^%s' % home, r"~", cwd) @@ -286,7 +284,7 @@ def workingdirbase(self): Equivalent to the bash prompt escape sequence ``\\W``. """ - return os.path.basename(self.status.getWorkingDir()) + return os.path.basename(self.status.get_working_dir()) def dollar(self, euid=None): """ @@ -314,7 +312,7 @@ def isrealpath(self, path=None): :rtype: bool """ if path is None: - path = self.status.getWorkingDir() + path = self.status.get_working_dir() if path == os.path.realpath(path): return True else: @@ -328,7 +326,7 @@ def exitsuccess(self): :rtype: bool """ - if self.status.exitCode == 0: + if self.status.exit_code == 0: return True else: return False @@ -518,15 +516,15 @@ def smiley(self): - ``#:)`` - (colour green) Last command succeeded, user is root - ``#:(`` - (colour red) Last command failed, user is root """ - if self.status.exitCode == 0: - out = self.call("startColour", "green", style="bold") + if self.status.exit_code == 0: + out = self.call("startcolour", "green", style="bold") out += self.call("dollar") out += str(":)") else: - out = self.call("startColour", "red", style="bold") + out = self.call("startcolour", "red", style="bold") out += self.call("dollar") out += str(":(") - out += self.call("stopColour") + out += self.call("stopcolour") return out def randomcolour(self, literal, seed=None): @@ -538,9 +536,9 @@ def randomcolour(self, literal, seed=None): if seed: random.seed(seed) colour = str(random.randrange(1, 255)) - out = self.call("startColour", colour) + out = self.call("startcolour", colour) out += literal - out += self.call("stopColour") + out += self.call("stopcolour") return out def hashedcolour(self, literal): diff --git a/prompty/git.py b/prompty/git.py index bef699a..5e62393 100644 --- a/prompty/git.py +++ b/prompty/git.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import os import time @@ -17,26 +16,26 @@ class Git(vcs.VCSBase): def __init__(self, status, cmd=GIT_COMMAND): super(Git, self).__init__(status, cmd) - def _runStatus(self): + def _run_status(self): try: - (stdout, stderr, returncode) = self.runCommand( + (stdout, stderr, returncode) = self.run_command( [self.command, "status", "--porcelain", "-b"] ) - (rstdout, rstderr, rreturncode) = self.runCommand( + (rstdout, rstderr, rreturncode) = self.run_command( [self.command, "rev-parse", "--show-cdup", "--verify", "--short", "HEAD"] ) except OSError: # Git command not found self.installed = False - self.isRepo = False + self.is_repo = False return if returncode == 0: # Successful git status call self.installed = True - self.isRepo = True + self.is_repo = True (self.branch, - self.remoteBranch, + self.remote_branch, self.staged, self.changed, self.untracked, @@ -47,17 +46,17 @@ def _runStatus(self): if "Not a git repository" in stderr: # The directory is not a git repo self.installed = True - self.isRepo = False + self.is_repo = False else: # Some other error? self.installed = False - self.isRepo = False + self.is_repo = False if rreturncode == 0: # Successful git status call self.relative_root, self.commit = rstdout.split('\n')[:-1] - if self.installed and self.isRepo: + if self.installed and self.is_repo: self._run_get_last_fetch() def _run_get_last_fetch(self): @@ -147,11 +146,11 @@ def _git_commit(self): Get git HEAD commit hash. """ git_cmd = [self.command, 'rev-parse', '--short', 'HEAD'] - return self.runCommand(git_cmd)[0].strip() + return self.run_command(git_cmd)[0].strip() def _git_tags(self): """ Gets any tags associated with the current HEAD """ git_cmd = [self.command, 'tag', '--points-at', 'HEAD'] - return ", ".join(line.strip() for line in self.runCommand(git_cmd)[0].strip().splitlines()) + return ", ".join(line.strip() for line in self.run_command(git_cmd)[0].strip().splitlines()) diff --git a/prompty/lexer.py b/prompty/lexer.py index 697886e..26781fd 100644 --- a/prompty/lexer.py +++ b/prompty/lexer.py @@ -3,8 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals -from builtins import chr # Import external modules import shlex @@ -25,19 +23,19 @@ class Lexer(shlex.shlex): COMMENT_CHAR = "%" def __init__(self, instream): - instream = self.fixComments(instream) - instream = self.fixLineNumbers(instream) + instream = self.fix_comments(instream) + instream = self.fix_line_numbers(instream) shlex.shlex.__init__(self, instream=instream) - asciiCharSet = set([chr(i) for i in range(128)]) + ascii_char_set = set([chr(i) for i in range(128)]) # Discard special chars for char in self.SPECIAL_CHARS: - asciiCharSet.discard(char) - self.wordchars = ''.join(asciiCharSet) + ascii_char_set.discard(char) + self.wordchars = ''.join(ascii_char_set) self.commenters = self.COMMENT_CHAR @staticmethod - def fixComments(instream): + def fix_comments(instream): """Fix for bug where newline at end of comment gets treated as part of the comment (causing 'A\n#comment\nB' to be rendered as 'AB' instead of the intended 'A,B'). @@ -51,7 +49,7 @@ def fixComments(instream): instream) @staticmethod - def fixLineNumbers(instream): + def fix_line_numbers(instream): """Fix broken line number reporting by ensuring that every line has a trailing whitespace character """ diff --git a/prompty/parser.py b/prompty/parser.py index 9bc74fd..3b69e1e 100644 --- a/prompty/parser.py +++ b/prompty/parser.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals from prompty import lexer diff --git a/prompty/prompt.py b/prompty/prompt.py index 9f68d8b..0c34368 100644 --- a/prompty/prompt.py +++ b/prompty/prompt.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals # Import external modules @@ -24,16 +23,16 @@ def __init__(self, status): self.funcs = functionContainer.FunctionContainer( self.status ) - self.funcs.addFunctionsFromModule(functions) - self.funcs.addFunctionsFromModule(colours) - self.funcs.addFunctionsFromModule(vcs) - self.funcs.addFunctionsFromDir(self.status.userDir.promtyUserFunctionsDir) + self.funcs.add_functions_from_module(functions) + self.funcs.add_functions_from_module(colours) + self.funcs.add_functions_from_module(vcs) + self.funcs.add_functions_from_dir(self.status.user_dir.promty_user_functions_dir) self.compiler = compiler.Compiler(self.funcs) self.config = config.Config() - self.config.load(self.status.userDir.getConfigFile()) + self.config.load(self.status.user_dir.get_config_file()) - def getPrompt(self): - self.compiler.compile(self.config.promptString) + def get_prompt(self): + self.compiler.compile(self.config.prompt_string) output = self.compiler.execute() return output diff --git a/prompty/status.py b/prompty/status.py index 3fe68f6..2e6e48b 100644 --- a/prompty/status.py +++ b/prompty/status.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import os import subprocess @@ -29,30 +28,30 @@ def __add__(self, other): return Coords(self.column+other.column, self.row+other.row) - def incRow(self, inc=1): + def inc_row(self, inc=1): self.row += inc - def incColumn(self, inc=1): + def inc_column(self, inc=1): self.column += inc - def resetRow(self): + def reset_row(self): self.row = 0 - def resetColumn(self): + def reset_column(self): self.column = 0 def set(self, other): self.column = other.column self.row = other.row - def incFromString(self, unicodeString): + def inc_from_string(self, unicode_string): """ Update the cursor position given movements from the input string. Adjust for any non-printing characters (these are encapsulated by the NOCOUNT_* characters from the Colour class) """ non_print = False - for char in unicodeString: + for char in unicode_string: if char == colours.Colours.NOCOUNT_START: non_print = True continue @@ -61,21 +60,22 @@ def incFromString(self, unicodeString): continue if not non_print: if char == "\n": - self.incRow() - self.resetColumn() + self.inc_row() + self.reset_column() elif char == "\r": - self.resetColumn() + self.reset_column() else: - self.incColumn() + self.inc_column() class Status(object): - def __init__(self, exitCode=0, workingDir=None): - self.exitCode = int(exitCode) - self.workingDir = workingDir - self.userDir = userdir.UserDir() + def __init__(self, exit_code=0, working_dir=None): + self.exit_code = int(exit_code) + self.working_dir = working_dir + self.user_dir = userdir.UserDir() self.euid = os.geteuid() self.vcs = vcs.VCS(self) + self.wrap = True proc = subprocess.Popen(['stty', 'size'], stdout=subprocess.PIPE, @@ -88,8 +88,8 @@ def __init__(self, exitCode=0, workingDir=None): self.window = Coords() self.pos = Coords() - def getWorkingDir(self): - if self.workingDir: - return os.path.normpath(self.workingDir) + def get_working_dir(self): + if self.working_dir: + return os.path.normpath(self.working_dir) else: return os.path.normpath(os.getenv('PWD')) diff --git a/prompty/svn.py b/prompty/svn.py index 4165b9d..ea12a0b 100644 --- a/prompty/svn.py +++ b/prompty/svn.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import re import xml.dom.minidom @@ -19,18 +18,18 @@ class Subversion(vcs.VCSBase): def __init__(self, status, cmd=SVN_COMMAND): super(Subversion, self).__init__(status, cmd) - def _runStatus(self): + def _run_status(self): try: - (istdout, istderr, _) = self.runCommand( + (istdout, istderr, _) = self.run_command( [self.command, "info", "--xml"] ) - (sstdout, sstderr, _) = self.runCommand( + (sstdout, sstderr, _) = self.run_command( [self.command, "status"] ) except OSError: # SVN command not found self.installed = False - self.isRepo = False + self.is_repo = False return if not istderr: @@ -41,11 +40,11 @@ def _runStatus(self): if "is not a working copy" in istderr: # The directory is not a svn repo self.installed = True - self.isRepo = False + self.is_repo = False else: # Some other error? self.installed = False - self.isRepo = False + self.is_repo = False if not sstderr: # Successful svn status call @@ -83,7 +82,7 @@ def _parse_info_xml(self, xml_string): # Error parsing xml return - self.isRepo = True + self.is_repo = True entry = info.documentElement.getElementsByTagName("entry")[0] diff --git a/prompty/userdir.py b/prompty/userdir.py index 641e1d3..068c5e4 100644 --- a/prompty/userdir.py +++ b/prompty/userdir.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import os import sys @@ -18,7 +17,7 @@ FUNCTIONS_DIR = "functions" -def getPromptyBaseDir(): +def get_prompty_base_dir(): """ Get the directory where the prompty module is located """ @@ -31,42 +30,42 @@ def getPromptyBaseDir(): class UserDir(object): - def __init__(self, homeDir=None): - if homeDir is None: - self.homeDir = os.path.expanduser('~') + def __init__(self, home_dir=None): + if home_dir is None: + self.home_dir = os.path.expanduser('~') else: - self.homeDir = homeDir - self.promtyUserDir = os.path.join(self.homeDir, PROMPTY_USER_DIR) - self.promtyBaseDir = getPromptyBaseDir() - self.promtyUserFunctionsDir = os.path.join(self.promtyUserDir, FUNCTIONS_DIR) + self.home_dir = home_dir + self.promty_user_dir = os.path.join(self.home_dir, PROMPTY_USER_DIR) + self.promty_base_dir = get_prompty_base_dir() + self.promty_user_functions_dir = os.path.join(self.promty_user_dir, FUNCTIONS_DIR) - self.skelDir = os.path.join(self.promtyBaseDir, SKEL_DIR) - if not os.path.exists(self.skelDir): + self.skel_dir = os.path.join(self.promty_base_dir, SKEL_DIR) + if not os.path.exists(self.skel_dir): # Installed locally - self.skelDir = os.path.join(self.homeDir, ".local", "share", "prompty", SKEL_DIR) + self.skel_dir = os.path.join(self.home_dir, ".local", "share", "prompty", SKEL_DIR) - if not os.path.exists(self.skelDir): + if not os.path.exists(self.skel_dir): # Install dir as defined in setup.py - self.skelDir = os.path.join(sys.prefix, "share", "prompty", SKEL_DIR) + self.skel_dir = os.path.join(sys.prefix, "share", "prompty", SKEL_DIR) - if not os.path.exists(self.skelDir): + if not os.path.exists(self.skel_dir): # Install dir as defined in setup.py - self.skelDir = os.path.join(sys.prefix, "local", "share", "prompty", SKEL_DIR) + self.skel_dir = os.path.join(sys.prefix, "local", "share", "prompty", SKEL_DIR) - if not os.path.exists(self.skelDir): + if not os.path.exists(self.skel_dir): raise IOError("Cannot find installed skel directory") - # Initialise if promptyUserDir does not exist + # Initialise if prompty_user_dir does not exist self.initialise() def initialise(self): - if not os.path.isfile(self.getConfigFile()): + if not os.path.isfile(self.get_config_file()): # No prompty dir - check if there is a file blocking the name - if os.path.isfile(self.promtyUserDir): + if os.path.isfile(self.promty_user_dir): raise IOError("Cannot create %s directory - file exists!" % PROMPTY_USER_DIR) # Create prompty dir from skel - self.copy(self.skelDir, self.promtyUserDir) + self.copy(self.skel_dir, self.promty_user_dir) @staticmethod def copy(src, dest): @@ -83,8 +82,8 @@ def copy(src, dest): else: raise IOError('Directory not copied. Error: %s' % e) - def getDir(self): - return self.promtyUserDir + def get_dir(self): + return self.promty_user_dir - def getConfigFile(self): - return os.path.join(self.promtyUserDir, PROMPTY_CONFIG_FILE) + def get_config_file(self): + return os.path.join(self.promty_user_dir, PROMPTY_CONFIG_FILE) diff --git a/prompty/vcs.py b/prompty/vcs.py index 1c3eef6..d3c24c8 100644 --- a/prompty/vcs.py +++ b/prompty/vcs.py @@ -3,12 +3,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import abc ABC = abc.ABCMeta(str('ABC'), (object,), {'__slots__': ()}) # noqa, compatible with Python 2 *and* 3 -from builtins import str import subprocess from prompty import functionBase @@ -21,40 +19,40 @@ class VCS(object): """ def __init__(self, status): self.status = status - self.vcsObjs = [] - self.ranStatus = False + self.vcs_objs = [] + self.ran_status = False self.cwd = None - self.populateVCS() - self.currentVcsObj = self.vcsObjs[0] + self.populate_vcs() + self.current_vcs_obj = self.vcs_objs[0] - def populateVCS(self): + def populate_vcs(self): # The order here defines the order in which repository # types are tested. The first one found to be a valid repo # will halt all further searching, so put them in priority # order. from . import git - self.vcsObjs.append(git.Git(self.status)) + self.vcs_objs.append(git.Git(self.status)) from . import svn - self.vcsObjs.append(svn.Subversion(self.status)) + self.vcs_objs.append(svn.Subversion(self.status)) def __getattribute__(self, name): """ If we have not yet run a status call then run one before - attempting to get the attribute. _runStatus() is also called + attempting to get the attribute. _run_status() is also called again if the working directory has changed. """ - if name in ["populateVCS", "vcsObjs", "ranStatus", "cwd", "currentVcsObj", "status"]: + if name in ["populate_vcs", "vcs_objs", "ran_status", "cwd", "current_vcs_obj", "status"]: return object.__getattribute__(self, name) - if not self.ranStatus or self.cwd != self.status.getWorkingDir(): - self.cwd = self.status.getWorkingDir() - self.ranStatus = True - for vcs in self.vcsObjs: - if vcs.isRepo: - self.currentVcsObj = vcs + if not self.ran_status or self.cwd != self.status.get_working_dir(): + self.cwd = self.status.get_working_dir() + self.ran_status = True + for vcs in self.vcs_objs: + if vcs.is_repo: + self.current_vcs_obj = vcs break - return getattr(object.__getattribute__(self, "currentVcsObj"), name) + return getattr(object.__getattribute__(self, "current_vcs_obj"), name) class VCSBase(ABC): @@ -65,11 +63,11 @@ class VCSBase(ABC): @abc.abstractmethod def __init__(self, status, cmd): self.status = status - self.ranStatus = False + self.ran_status = False self.command = cmd self.cwd = None self.branch = "" - self.remoteBranch = "" + self.remote_branch = "" self.staged = 0 self.changed = 0 self.untracked = 0 @@ -77,13 +75,13 @@ def __init__(self, status, cmd): self.ahead = 0 self.behind = 0 self.installed = None - self.isRepo = None + self.is_repo = None self.commit = "" self.last_fetched = 0 self.relative_root = "" @abc.abstractmethod - def _runStatus(self): + def _run_status(self): """ Method is abstract and must be implemented. This method sets appropriately all of the member variables defined @@ -94,24 +92,24 @@ def _runStatus(self): def __getattribute__(self, name): """ If we have not yet run a status call then run one before - attempting to get the attribute. _runStatus() is also called + attempting to get the attribute. _run_status() is also called again if the working directory has changed. """ - if name in ["ranStatus", "cwd", "status"]: + if name in ["ran_status", "cwd", "status"]: return object.__getattribute__(self, name) - if not self.ranStatus or self.cwd != self.status.getWorkingDir(): - self.cwd = self.status.getWorkingDir() - self.ranStatus = True - self._runStatus() + if not self.ran_status or self.cwd != self.status.get_working_dir(): + self.cwd = self.status.get_working_dir() + self.ran_status = True + self._run_status() return object.__getattribute__(self, name) - def runCommand(self, cmdList): + def run_command(self, cmd_list): # Raises OSError if command doesn't exist - proc = subprocess.Popen(cmdList, + proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - cwd=self.status.getWorkingDir()) + cwd=self.status.get_working_dir()) stdout, stderr = proc.communicate() return stdout.decode('utf-8'), stderr.decode('utf-8'), proc.returncode @@ -127,7 +125,7 @@ def isrepo(self): :rtype: bool """ - return self.status.vcs.isRepo + return self.status.vcs.is_repo def repobranch(self): """ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..19d2492 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +click>=7.* +configparser +future \ No newline at end of file diff --git a/setup.py b/setup.py index d478141..95b71dc 100644 --- a/setup.py +++ b/setup.py @@ -105,6 +105,7 @@ def run(self): install_requires=[ "future", - "configparser" + "configparser", + "click" ], ) diff --git a/skel/default.prompty b/skel/default.prompty index 83d578a..259236d 100644 --- a/skel/default.prompty +++ b/skel/default.prompty @@ -19,49 +19,49 @@ \ifexpr{\isrealpath}{}{\space\warning{(!)}} \ifexpr{\isrepo}{ \space - \ifexpr{\isrepodirty}{\startColour{error}} { - \ifexpr{\behind}{\startColour{warning}}{ - \ifexpr{\ahead}{\startColour{warning}}{ - \startColour{info1} + \ifexpr{\isrepodirty}{\startcolour{error}} { + \ifexpr{\behind}{\startcolour{warning}}{ + \ifexpr{\ahead}{\startcolour{warning}}{ + \startcolour{info1} } } } ( \repobranch ) - \stopColour + \stopcolour \space \dim3{\opensquare} - \ifexpr{\gt{\last_fetched_min}{60}}{\startColour{warning}} {\startColour{dim3}} + \ifexpr{\gt{\last_fetched_min}{60}}{\startcolour{warning}} {\startcolour{dim3}} \unichar{0x29D7}\space\last_fetched_min - \stopColour + \stopcolour \space - \ifexpr{\behind}{\startColour{warning}} {\startColour{dim3}} + \ifexpr{\behind}{\startcolour{warning}} {\startcolour{dim3}} \unichar{0x25be}\behind - \stopColour + \stopcolour \space \dim3{# \commit} \space - \ifexpr{\ahead}{\startColour{warning}} {\startColour{dim3}} + \ifexpr{\ahead}{\startcolour{warning}} {\startcolour{dim3}} \unichar{0x25b4}\ahead - \stopColour + \stopcolour \space - \ifexpr{\staged}{\startColour{warning}} {\startColour{dim3}} + \ifexpr{\staged}{\startcolour{warning}} {\startcolour{dim3}} \unichar{0x2714}\space\staged - \stopColour + \stopcolour \space - \ifexpr{\changed}{\startColour{warning}} {\startColour{dim3}} + \ifexpr{\changed}{\startcolour{warning}} {\startcolour{dim3}} \unichar{0x270e}\space\changed - \stopColour + \stopcolour \space - \ifexpr{\untracked}{\startColour{warning}} {\startColour{dim3}} + \ifexpr{\untracked}{\startcolour{warning}} {\startcolour{dim3}} ?\untracked - \stopColour + \stopcolour \dim3{\closesquare} }{} \newline diff --git a/skel/powerline.prompty b/skel/powerline.prompty new file mode 100644 index 0000000..5610d78 --- /dev/null +++ b/skel/powerline.prompty @@ -0,0 +1,33 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% vim: set filetype=tex: +% +% Powerline prompty +% ============= +% A promty script utilising the Powerline fonts +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\ifexpr{\exitsuccess}{ + \cyan{\unichar{0x250f}\unichar{0x2501}} +}{ + \red{\unichar{0x250f}\unichar{0x2501}} +} +\powerline[232][cyan][cyan]{\user @ \hostname} +\ifexpr{\isrepo}{ + \ifexpr{\isrepodirty}{ + \powerline[cyan][yellow][232]{\workingdir} + \powerline[yellow][default][232]{\plbranch\space\repobranch\space\unichar{0x26a0}} + }{ + \powerline[cyan][lightgreen][232]{\workingdir} + \powerline[lightgreen][default][232]{\plbranch\space\repobranch} + } +}{ + \powerline[cyan][default][232]{\workingdir} +} +\newline +\ifexpr{\exitsuccess}{ + \cyan{\unichar{0x2517}\plrightarrowfill} +}{ + \red{\unichar{0x2517}\plrightarrowfill} +} +\space \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py index a5447d4..a6e6c78 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -4,6 +4,7 @@ import sys import imp import unittest +from unittest.util import safe_repr TEST_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/test/__main__.py b/test/__main__.py index c9e96e2..3d32b63 100644 --- a/test/__main__.py +++ b/test/__main__.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import sys import unittest diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 0000000..615fac9 --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,2 @@ +mock +pytest \ No newline at end of file diff --git a/test/test_colours.py b/test/test_colours.py index 385583b..c3edb98 100644 --- a/test/test_colours.py +++ b/test/test_colours.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals from test import prompty from test import UnitTestWrapper @@ -13,87 +12,90 @@ class ColourTests(UnitTestWrapper): def test_getColourCode4Bit(self): c = prompty.colours.Colours(None) - self.assertEqual(c.RED[c.VAL_KEY], c._getColourCode(c.RED)) - self.assertEqual(c.BLACK[c.VAL_KEY], c._getColourCode("black")) - self.assertEqual(c.MAGENTA[c.VAL_KEY], c._getColourCode("m")) + self.assertEqual(c.RED[c.VAL_KEY], c._get_colour_code(c.RED)) + self.assertEqual(c.BLACK[c.VAL_KEY], c._get_colour_code("black")) + self.assertEqual(c.MAGENTA[c.VAL_KEY], c._get_colour_code("m")) for colour in c.COLOURS: - self.assertEqual(colour[c.VAL_KEY], c._getColourCode(colour)) - self.assertEqual(colour[c.VAL_KEY], c._getColourCode(colour[c.NAME_KEY])) - self.assertEqual(colour[c.VAL_KEY], c._getColourCode(colour[c.CODE_KEY])) - self.assertRaises(ValueError, c._getColourCode, "burple") - self.assertRaises(ValueError, c._getColourCode, "") + self.assertEqual(colour[c.VAL_KEY], c._get_colour_code(colour)) + self.assertEqual(colour[c.VAL_KEY], c._get_colour_code(colour[c.NAME_KEY])) + self.assertEqual(colour[c.VAL_KEY], c._get_colour_code(colour[c.CODE_KEY])) + self.assertRaises(ValueError, c._get_colour_code, "burple") + self.assertRaises(ValueError, c._get_colour_code, "") def test_getColourCodeBg4Bit(self): c = prompty.colours.Colours(None) for colour in c.COLOURS: self.assertEqual(int(colour[c.VAL_KEY])+c.BG_OFFSET, - c._getColourCode(colour, c.BACKGROUND)) + c._get_colour_code(colour, c.BACKGROUND)) self.assertEqual(int(colour[c.VAL_KEY])+c.BG_OFFSET, - c._getColourCode(colour[c.NAME_KEY], c.BACKGROUND)) + c._get_colour_code(colour[c.NAME_KEY], c.BACKGROUND)) self.assertEqual(int(colour[c.VAL_KEY])+c.BG_OFFSET, - c._getColourCode(colour[c.CODE_KEY], c.BACKGROUND)) - self.assertRaises(ValueError, c._getColourCode, "burple") + c._get_colour_code(colour[c.CODE_KEY], c.BACKGROUND)) + self.assertRaises(ValueError, c._get_colour_code, "burple") def test_getColourCode8Bit(self): c = prompty.colours.Colours(None) - self.assertEqual("38;5;145", c._getColourCode("145")) - self.assertEqual("38;5;0", c._getColourCode("0")) - self.assertEqual("38;5;255", c._getColourCode("255")) - self.assertRaises(ValueError, c._getColourCode, "256") - self.assertRaises(ValueError, c._getColourCode, "0x456") - self.assertEqual("38;5;16", c._getColourCode("#000")) - self.assertEqual("38;5;196", c._getColourCode("#f00")) - self.assertEqual("38;5;46", c._getColourCode("#0f0")) - self.assertEqual("38;5;21", c._getColourCode("#00f")) - self.assertRaises(ValueError, c._getColourCode, "#bat") - self.assertEqual("38;5;231", c._getColourCode("#gff")) - self.assertEqual("38;5;16", c._getColourCode("#g00")) - self.assertEqual("38;5;232", c._getColourCode("#g05")) - self.assertEqual("38;5;239", c._getColourCode("#g4e")) - self.assertEqual("38;5;255", c._getColourCode("#gee")) + self.assertEqual("38;5;145", c._get_colour_code("145")) + self.assertEqual("38;5;0", c._get_colour_code("0")) + self.assertEqual("38;5;255", c._get_colour_code("255")) + self.assertRaises(ValueError, c._get_colour_code, "256") + self.assertRaises(ValueError, c._get_colour_code, "0x456") + self.assertEqual("38;5;16", c._get_colour_code("#000")) + self.assertEqual("38;5;196", c._get_colour_code("#f00")) + self.assertEqual("38;5;46", c._get_colour_code("#0f0")) + self.assertEqual("38;5;21", c._get_colour_code("#00f")) + self.assertRaises(ValueError, c._get_colour_code, "#bat") + self.assertEqual("38;5;231", c._get_colour_code("#gff")) + self.assertEqual("38;5;16", c._get_colour_code("#g00")) + self.assertEqual("38;5;232", c._get_colour_code("#g05")) + self.assertEqual("38;5;239", c._get_colour_code("#g4e")) + self.assertEqual("38;5;255", c._get_colour_code("#gee")) def test_getColourCodeBg8Bit(self): c = prompty.colours.Colours(None) - self.assertEqual("48;5;145", c._getColourCode("145", c.BACKGROUND)) + self.assertEqual("48;5;145", c._get_colour_code("145", c.BACKGROUND)) def test_getColourCode24Bit(self): c = prompty.colours.Colours(None) - self.assertEqual("38;2;0;0;0", c._getColourCode("#000000")) - self.assertEqual("38;2;1;2;3", c._getColourCode("#010203")) - self.assertEqual("38;2;255;255;255", c._getColourCode("#ffffff")) - self.assertEqual("38;2;0;0;0", c._getColourCode("0,0,0")) - self.assertEqual("38;2;1;2;3", c._getColourCode("1,2,3")) - self.assertEqual("38;2;255;255;255", c._getColourCode("255,255,255")) - self.assertRaises(ValueError, c._getColourCode, "0,0") + self.assertEqual("38;2;0;0;0", c._get_colour_code("#000000")) + self.assertEqual("38;2;1;2;3", c._get_colour_code("#010203")) + self.assertEqual("38;2;255;255;255", c._get_colour_code("#ffffff")) + self.assertEqual("38;2;0;0;0", c._get_colour_code("0,0,0")) + self.assertEqual("38;2;1;2;3", c._get_colour_code("1,2,3")) + self.assertEqual("38;2;255;255;255", c._get_colour_code("255,255,255")) + self.assertRaises(ValueError, c._get_colour_code, "0,0") def test_getColourCodeBg24Bit(self): c = prompty.colours.Colours(None) - self.assertEqual("48;2;1;2;3", c._getColourCode("#010203", c.BACKGROUND)) - self.assertEqual("48;2;1;2;3", c._getColourCode("1,2,3", c.BACKGROUND)) + self.assertEqual("48;2;1;2;3", c._get_colour_code("#010203", c.BACKGROUND)) + self.assertEqual("48;2;1;2;3", c._get_colour_code("1,2,3", c.BACKGROUND)) def test_getStyleCode(self): c = prompty.colours.Colours(None) - self.assertIs(c._getStyleCode(c.NORMAL), c.NORMAL[c.VAL_KEY]) - self.assertIs(c._getStyleCode("italic"), c.ITALIC[c.VAL_KEY]) - self.assertIs(c._getStyleCode("b"), c.BOLD[c.VAL_KEY]) + self.assertIs(c._get_style_code(c.NORMAL), c.NORMAL[c.VAL_KEY]) + self.assertIs(c._get_style_code("italic"), c.ITALIC[c.VAL_KEY]) + self.assertIs(c._get_style_code("b"), c.BOLD[c.VAL_KEY]) for style in c.STYLES: - self.assertEqual(c._getStyleCode(style), style[c.VAL_KEY]) - self.assertEqual(c._getStyleCode(style[c.NAME_KEY]), style[c.VAL_KEY]) - self.assertEqual(c._getStyleCode(style[c.CODE_KEY]), style[c.VAL_KEY]) - self.assertRaises(KeyError, c._getStyleCode, "upsidedown") + self.assertEqual(c._get_style_code(style), style[c.VAL_KEY]) + self.assertEqual(c._get_style_code(style[c.NAME_KEY]), style[c.VAL_KEY]) + self.assertEqual(c._get_style_code(style[c.CODE_KEY]), style[c.VAL_KEY]) + self.assertRaises(KeyError, c._get_style_code, "upsidedown") def test_stopColour(self): c = prompty.colours.Colours(None) - self.assertEqual(c.stopColour(), "\001\033[0m\002") - self.assertEqual(c.stopColour(False), "\033[0m") + self.assertEqual(c.stopcolour(), "\001\033[0m\002") + c.status.wrap = False + self.assertEqual(c.stopcolour(), "\033[0m") def test_startColour(self): c = prompty.colours.Colours(None) - self.assertEqual(c.startColour("green"), "\001\033[32m\002") - self.assertEqual(c.startColour("green", _wrap=False), "\033[32m") - self.assertEqual(c.startColour("red", style="b"), "\001\033[1;31m\002") - self.assertEqual(c.startColour("1"), "\001\033[38;5;1m\002") - self.assertEqual(c.startColour(fgcolour="1", bgcolour="2"), "\001\033[38;5;1;48;5;2m\002") + self.assertEqual(c.startcolour("green"), "\001\033[32m\002") + c.status.wrap = False + self.assertEqual(c.startcolour("green"), "\033[32m") + c.status.wrap = True + self.assertEqual(c.startcolour("red", style="b"), "\001\033[1;31m\002") + self.assertEqual(c.startcolour("1"), "\001\033[38;5;1m\002") + self.assertEqual(c.startcolour(fgcolour="1", bgcolour="2"), "\001\033[38;5;1;48;5;2m\002") def test_dynamicColourWrappers(self): c = prompty.colours.Colours(None) @@ -104,7 +106,7 @@ def test_dynamicColourWrappers(self): class ColourFunctionTests(UnitTestWrapper): def test_colourLiteral(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.colours) + c.add_functions_from_module(prompty.colours) self.assertEqual("\001\033[32m\002I'm green\001\033[0m\002", c._call("green", "I'm green")) self.assertEqual("\001\033[31m\002I'm red\001\033[0m\002", c._call("red", "I'm red")) @@ -112,15 +114,15 @@ def test_colourLiteral(self): class PaletteTests(UnitTestWrapper): def test_defaultPalette(self): c = prompty.colours.Colours(None) - self.assertEqual(c.startColour("info1"), "\001\033[32m\002") - self.assertEqual(c.startColour("info2"), "\001\033[94m\002") - self.assertEqual(c.startColour("warning"), "\001\033[33m\002") + self.assertEqual(c.startcolour("info1"), "\001\033[32m\002") + self.assertEqual(c.startcolour("info2"), "\001\033[94m\002") + self.assertEqual(c.startcolour("warning"), "\001\033[33m\002") def test_editPalette(self): c = prompty.colours.Colours(None) - c._setPalette("info1", c.RED) - self.assertEqual(c.startColour("info1"), "\001\033[31m\002") - c._setPalette("info1", "123") - self.assertEqual(c.startColour("info1"), "\001\033[38;5;123m\002") - c._setPalette("mypal", c.GREEN) - self.assertEqual(c.startColour("mypal"), "\001\033[32m\002") + c._set_palette("info1", c.RED) + self.assertEqual(c.startcolour("info1"), "\001\033[31m\002") + c._set_palette("info1", "123") + self.assertEqual(c.startcolour("info1"), "\001\033[38;5;123m\002") + c._set_palette("mypal", c.GREEN) + self.assertEqual(c.startcolour("mypal"), "\001\033[32m\002") diff --git a/test/test_functionContainer.py b/test/test_functionContainer.py index e1dffcb..3d0d7f5 100644 --- a/test/test_functionContainer.py +++ b/test/test_functionContainer.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import sys import os @@ -13,10 +12,10 @@ class MyFunctions(prompty.functionBase.PromptyFunctions): - def testFunc(self): + def test_func(self): return "This Is A Test" - def _hiddenFunc(self): + def _hidden_func(self): return "This is secret" @@ -28,12 +27,12 @@ def test_noname(self): def test_extendFunctionContainer(self): c = prompty.functionContainer.FunctionContainer() # Import this module - c.addFunctionsFromModule(sys.modules[__name__]) - self.assertEqual(r"This Is A Test", c._call("testFunc")) - self.assertRaises(KeyError, c._call, "_hiddenFunc") + c.add_functions_from_module(sys.modules[__name__]) + self.assertEqual(r"This Is A Test", c._call("test_func")) + self.assertRaises(KeyError, c._call, "_hidden_func") def test_extendFunctionContainerFromDir(self): c = prompty.functionContainer.FunctionContainer() # Import this directory - c.addFunctionsFromDir(os.path.dirname(sys.modules[__name__].__file__)) - self.assertEqual(r"This Is A Test", c._call("testFunc")) + c.add_functions_from_dir(os.path.dirname(sys.modules[__name__].__file__)) + self.assertEqual(r"This Is A Test", c._call("test_func")) diff --git a/test/test_functions.py b/test/test_functions.py index 6ad43a5..fbbaf69 100644 --- a/test/test_functions.py +++ b/test/test_functions.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals from builtins import chr import sys @@ -24,23 +23,23 @@ class StandardFunctionTests(UnitTestWrapper): def test_user(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual(getpass.getuser(), c._call("user")) def test_hostname(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual(socket.gethostname().split(".")[0], c._call("hostname")) def test_hostnamefull(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual(socket.gethostname(), c._call("hostnamefull")) def test_workingdir(self): origcwd = os.getcwd() c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) os.chdir(os.path.expanduser("~")) os.environ["PWD"] = os.getcwd() self.assertEqual(r"~", c._call("workingdir")) @@ -56,7 +55,7 @@ def test_workingdir(self): def test_workingdirbase(self): origcwd = os.getcwd() c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) tmpDir = tempfile.mkdtemp() os.chdir(tmpDir) os.environ["PWD"] = os.getcwd() @@ -71,13 +70,13 @@ def test_workingdirbase(self): def test_dollar(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual(r"$", c._call("dollar")) self.assertEqual(r"#", c._call("dollar", 0)) def test_specialChars(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) chars = [ ("newline", "\n"), ("carriagereturn", "\r"), @@ -95,21 +94,21 @@ def test_specialChars(self): def test_uniChar(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual('a', c._call("unichar", "97")) self.assertEqual('b', c._call("unichar", "0x62")) self.assertEqual('c', c._call("unichar", "0o143")) def test_lower(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual('lower', c._call("lower", "lower")) self.assertEqual('mixed', c._call("lower", "MiXEd")) self.assertEqual('all_upper with spaces', c._call("lower", "ALL_UPPER WITH SPACES")) def test_join(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual('one:fish', c._call("join", ":", "one", "fish")) self.assertEqual('one/fish/cheese', c._call("join", "/", "one", "fish", "cheese")) self.assertEqual('', c._call("join", "/")) @@ -127,7 +126,7 @@ def test_justify(self, mock_sp): mock_sp.Popen.return_value = MockProc(output) c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual('| | |', c._call("justify", "|", "|", "|")) self.assertEqual('|-----|=====|', c._call("justify", "|", "|", "|", "-", "=")) self.assertEqual(' |', c._call("right", "|")) @@ -135,28 +134,28 @@ def test_justify(self, mock_sp): def test_smiley(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) - c.addFunctionsFromModule(prompty.colours) - c.status.exitCode = 0 + c.add_functions_from_module(prompty.functions) + c.add_functions_from_module(prompty.colours) + c.status.exit_code = 0 self.assertIn(":)", c._call("smiley")) - c.status.exitCode = 1 + c.status.exit_code = 1 self.assertIn(":(", c._call("smiley")) def test_powerline(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) - c.addFunctionsFromModule(prompty.colours) + c.add_functions_from_module(prompty.functions) + c.add_functions_from_module(prompty.colours) self.assertIn("test", c._call("powerline", "test")) self.assertIn(chr(0xe0b0), c._call("powerline", "test")) def test_date(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertTrue(bool(re.match(r"^[a-zA-z]+ [a-zA-z]+ [0-9]+$", c._call("date")))) def test_datefmt(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertTrue(bool(re.match(r"^[0-9:]+$", c._call("datefmt")))) self.assertTrue(bool(re.match(r"^hello$", c._call("datefmt", "hello")))) self.assertTrue(bool(re.match(r"^[0-9]{2}$", c._call("datefmt", "#d")))) @@ -164,7 +163,7 @@ def test_datefmt(self): def test_isRealPath(self): origcwd = os.getcwd() c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertTrue(c._call("isrealpath")) tmpDir = tempfile.mkdtemp() link = os.path.join(tmpDir, "link") @@ -181,19 +180,19 @@ def test_isRealPath(self): class ExpressionFunctionTests(UnitTestWrapper): def test_equal(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual(True, c._call("equals", "1", "1")) def test_max(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual("2", c._call("max", "2", "1")) self.assertEqual("1", c._call("max", "1", "1")) self.assertEqual("2", c._call("max", "1", "2")) def test_if(self): c = prompty.functionContainer.FunctionContainer() - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual("1", c._call("ifexpr", "True", "1", "2")) self.assertEqual("2", c._call("ifexpr", "False", "1", "2")) self.assertEqual("1", c._call("ifexpr", "True", "1")) @@ -202,8 +201,8 @@ def test_if(self): def test_exitSuccess(self): c = prompty.functionContainer.FunctionContainer(prompty.status.Status(0)) - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual(True, c._call("exitsuccess")) c = prompty.functionContainer.FunctionContainer(prompty.status.Status(1)) - c.addFunctionsFromModule(prompty.functions) + c.add_functions_from_module(prompty.functions) self.assertEqual(False, c._call("exitsuccess")) diff --git a/test/test_parser.py b/test/test_parser.py index 6189558..257d052 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import socket import getpass @@ -97,33 +96,33 @@ def test_comments(self): def test_fixComments(self): i = "% comment" o = " % comment" - self.assertEqual(o, prompty.lexer.Lexer.fixComments(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_comments(i)) i = "A% comment\nB" o = "A % comment\nB" - self.assertEqual(o, prompty.lexer.Lexer.fixComments(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_comments(i)) i = "% comment\nB %comment\n%comment" o = " % comment\nB %comment\n %comment" - self.assertEqual(o, prompty.lexer.Lexer.fixComments(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_comments(i)) i = "\\myfunc{% comment\n\targ% comment\n}% comment" o = "\\myfunc{ % comment\n\targ % comment\n} % comment" - self.assertEqual(o, prompty.lexer.Lexer.fixComments(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_comments(i)) def test_fixLineNumbers(self): i = "% comment" o = i - self.assertEqual(o, prompty.lexer.Lexer.fixLineNumbers(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_line_numbers(i)) i = "% comment\n" o = "% comment \n" - self.assertEqual(o, prompty.lexer.Lexer.fixLineNumbers(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_line_numbers(i)) i = "\nA\nB" o = " \nA \nB" - self.assertEqual(o, prompty.lexer.Lexer.fixLineNumbers(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_line_numbers(i)) i = "\r\nA\r\nB" o = " \r\nA \r\nB" - self.assertEqual(o, prompty.lexer.Lexer.fixLineNumbers(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_line_numbers(i)) i = " \nA \nB" o = " \nA \nB" - self.assertEqual(o, prompty.lexer.Lexer.fixLineNumbers(i)) + self.assertEqual(o, prompty.lexer.Lexer.fix_line_numbers(i)) def test_lineNumbers(self): s = r"""% Comment (line 1) @@ -339,7 +338,7 @@ def test_external(self): c = prompty.compiler.Compiler(funcs) c.compile("literal1") self.assertEqual(r"literal1", c.execute()) - c.compile("literal2") + c.compile("literal2", clear=False) self.assertEqual(r"literal1literal2", c.execute()) def test_statusLength(self): @@ -352,7 +351,7 @@ def test_statusLength(self): def test_statusLength2(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) + funcs.add_functions_from_module(prompty.functions) c = prompty.compiler.Compiler(funcs) c.compile(r"a b\newline") c.execute() @@ -361,29 +360,29 @@ def test_statusLength2(self): def test_singleLiteral(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) + funcs.add_functions_from_module(prompty.functions) c = prompty.compiler.Compiler(funcs) self.assertEqual(r"literalvalue", c._execute( [{'lineno': 1, 'type': 'literal', 'value': r"literalvalue"}])) def test_multipleLiteral(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) + funcs.add_functions_from_module(prompty.functions) c = prompty.compiler.Compiler(funcs) self.assertEqual(r"literalvalue", c._execute([{'lineno': 1, 'type': 'literal', 'value': r"literal"}, {'lineno': 1, 'type': 'literal', 'value': r"value"}])) def test_singleFunction(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) + funcs.add_functions_from_module(prompty.functions) c = prompty.compiler.Compiler(funcs) self.assertEqual(CompilerTests.user, c._execute( [{'lineno': 1, 'type': 'function', 'name': r"user"}])) def test_nestedFunction(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) - funcs.addFunctionsFromModule(prompty.colours) + funcs.add_functions_from_module(prompty.functions) + funcs.add_functions_from_module(prompty.colours) c = prompty.compiler.Compiler(funcs) self.assertEqual("\001\033[32m\002%s\001\033[0m\002" % CompilerTests.user, c._execute([{'lineno': 1, 'type': 'function', 'name': r"green", 'args': @@ -391,8 +390,8 @@ def test_nestedFunction(self): def test_functionWithMultipleLiteralArgument(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) - funcs.addFunctionsFromModule(prompty.colours) + funcs.add_functions_from_module(prompty.functions) + funcs.add_functions_from_module(prompty.colours) c = prompty.compiler.Compiler(funcs) self.assertEqual("\001\033[32m\002a%sb%s\001\033[0m\002" % (CompilerTests.user, CompilerTests.host), c._execute([{'lineno': 1, 'type': 'function', 'name': r"green", 'args': @@ -406,8 +405,8 @@ def test_functionWithMultipleLiteralArgument(self): def test_nestedFunctionOptionalArg(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) - funcs.addFunctionsFromModule(prompty.colours) + funcs.add_functions_from_module(prompty.functions) + funcs.add_functions_from_module(prompty.colours) c = prompty.compiler.Compiler(funcs) self.assertEqual("\001\033[1;32m\002%s\001\033[0m\002" % CompilerTests.user, c._execute([{'lineno': 1, 'type': 'function', 'name': r"green", 'args': @@ -417,7 +416,7 @@ def test_nestedFunctionOptionalArg(self): def test_multipleAruments(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) + funcs.add_functions_from_module(prompty.functions) c = prompty.compiler.Compiler(funcs) self.assertEqual(r"2", c._execute([{'lineno': 1, 'type': 'function', 'name': r"max", 'args': [[{'lineno': 1, 'type': 'literal', 'value': r"1"}], @@ -427,7 +426,7 @@ def test_multipleAruments(self): def test_emptyAruments(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) + funcs.add_functions_from_module(prompty.functions) c = prompty.compiler.Compiler(funcs) self.assertEqual("..", c._execute([{'lineno': 1, 'type': 'function', 'name': r"join", 'args': [[{'lineno': 1, 'type': 'literal', 'value': r"."}], @@ -442,8 +441,8 @@ def test_emptyAruments(self): def test_equalFunction(self): funcs = prompty.functionContainer.FunctionContainer() - funcs.addFunctionsFromModule(prompty.functions) - funcs.addFunctionsFromModule(prompty.colours) + funcs.add_functions_from_module(prompty.functions) + funcs.add_functions_from_module(prompty.colours) c = prompty.compiler.Compiler(funcs) self.assertEqual("True", c._execute([{'args': [[{'lineno': 1, 'type': 'literal', 'value': '1'}], [{'lineno': 1, 'type': 'literal', 'value': '1'}]], diff --git a/test/test_prompty.py b/test/test_prompty.py index b0eb5db..74af67c 100755 --- a/test/test_prompty.py +++ b/test/test_prompty.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals from builtins import str # Import external modules @@ -14,6 +13,7 @@ import shutil import tempfile import unittest +import click.testing import distutils.spawn from contextlib import contextmanager from io import StringIO @@ -24,81 +24,64 @@ _MAX_LENGTH = 80 -def safe_repr(obj, short=False): - try: - result = repr(obj) - except Exception: - result = object.__repr__(obj) - if not short or len(result) < _MAX_LENGTH: - return result - return result[:_MAX_LENGTH] + ' [truncated]...' - - -@contextmanager -def captured_output(): - new_out, new_err = StringIO(), StringIO() - old_out, old_err = sys.stdout, sys.stderr - try: - sys.stdout, sys.stderr = new_out, new_err - yield sys.stdout, sys.stderr - finally: - sys.stdout, sys.stderr = old_out, old_err - - class MainTests(UnitTestWrapper): def test_help(self): - argv = ["", "-h"] - with captured_output() as (out, err): - ret = prompty_bin.main(argv) + argv = ["--help"] + runner = click.testing.CliRunner(mix_stderr=False) + result = runner.invoke(prompty_bin.prompty.cli.cli, argv) - self.assertEqual(out.getvalue(), "") - self.assertGreater(len(err.getvalue()), 0) - self.assertEqual(ret, 0) + self.assertGreater(len(result.output), 0) + self.assertEqual(len(result.stderr), 0) + self.assertEqual(result.exit_code, 0) def test_bash(self): - argv = ["", "-b"] - with captured_output() as (out, err): - ret = prompty_bin.main(argv) + argv = ["gen-bashrc"] + runner = click.testing.CliRunner(mix_stderr=False) + result = runner.invoke(prompty_bin.prompty.cli.cli, argv) - self.assertTrue(out.getvalue().startswith("export PS1")) - self.assertEqual(err.getvalue(), "") - self.assertEqual(ret, 0) + self.assertTrue(result.output.startswith("export PS1")) + self.assertEqual(len(result.stderr), 0) + self.assertEqual(result.exit_code, 0) def test_prompty(self): - argv = ["", "1"] - with captured_output() as (out, err): - ret = prompty_bin.main(argv) + argv = ["-e 1"] + runner = click.testing.CliRunner(mix_stderr=False) + result = runner.invoke(prompty_bin.prompty.cli.cli, argv) + + print("********") + print(result) + print("********") - self.assertGreater(len(out.getvalue()), 0) - self.assertEqual(len(err.getvalue()), 0) - self.assertEqual(ret, 1) + self.assertGreater(len(result.output), 0) + self.assertEqual(len(result.stderr), 0) + self.assertEqual(result.exit_code, 1) def test_invalid(self): - argv = ["", "-@"] - with captured_output() as (out, err): - ret = prompty_bin.main(argv) + argv = ["-@"] + runner = click.testing.CliRunner(mix_stderr=False) + result = runner.invoke(prompty_bin.prompty.cli.cli, argv) - self.assertEqual(out.getvalue(), "") - self.assertGreater(len(err.getvalue()), 0) - self.assertEqual(ret, 1) + self.assertEqual(result.output, "") + self.assertGreater(len(result.stderr), 0) + self.assertEqual(result.exit_code, 2) def test_colours(self): - argv = ["", "-c"] - with captured_output() as (out, err): - ret = prompty_bin.main(argv) + argv = ["colours"] + runner = click.testing.CliRunner(mix_stderr=False) + result = runner.invoke(prompty_bin.prompty.cli.cli, argv) - self.assertGreater(len(out.getvalue().splitlines()), 1) - self.assertEqual(err.getvalue(), "") - self.assertEqual(ret, 0) + self.assertGreater(len(result.output.splitlines()), 1) + self.assertEqual(len(result.stderr), 0) + self.assertEqual(result.exit_code, 0) def test_pallete(self): - argv = ["", "-p"] - with captured_output() as (out, err): - ret = prompty_bin.main(argv) + argv = ["palette"] + runner = click.testing.CliRunner(mix_stderr=False) + result = runner.invoke(prompty_bin.prompty.cli.cli, argv) - self.assertGreater(len(out.getvalue().splitlines()), 1) - self.assertEqual(err.getvalue(), "") - self.assertEqual(ret, 0) + self.assertGreater(len(result.output.splitlines()), 1) + self.assertEqual(len(result.stderr), 0) + self.assertEqual(result.exit_code, 0) class CoordsTests(UnitTestWrapper): @@ -129,7 +112,7 @@ def test_create(self): def test_getPrompt(self): p = prompty.prompt.Prompt(prompty.status.Status()) - s = p.getPrompt() + s = p.get_prompt() self.assertIsInstance(s, str) self.assertGreater(len(s), 0) @@ -139,21 +122,21 @@ def test_userDirLocation(self): u = prompty.userdir.UserDir() self.assertEqual( os.path.join(os.path.expanduser('~'), prompty.userdir.PROMPTY_USER_DIR), - u.getDir() + u.get_dir() ) def test_functionsDirLocation(self): u = prompty.userdir.UserDir() self.assertEqual( os.path.join(os.path.expanduser('~'), prompty.userdir.PROMPTY_USER_DIR, prompty.userdir.FUNCTIONS_DIR), - u.promtyUserFunctionsDir + u.promty_user_functions_dir ) def test_initialise(self): tmpDir = tempfile.mkdtemp() u = prompty.userdir.UserDir(tmpDir) - self.assertTrue(os.path.isdir(u.getDir())) - self.assertTrue(os.path.exists(u.getConfigFile())) + self.assertTrue(os.path.isdir(u.get_dir())) + self.assertTrue(os.path.exists(u.get_config_file())) # Cleanup shutil.rmtree(tmpDir) @@ -187,11 +170,11 @@ def test_loadConfig(self): prompty.userdir.PROMPTY_CONFIG_FILE)) self.assertEqual( os.path.join(os.path.dirname(TEST_DIR), prompty.userdir.SKEL_DIR, "default.prompty"), - c.promptFile + c.prompt_file ) def test_loadPrompt(self): c = prompty.config.Config() - c.promptFile = os.path.join(os.path.dirname(TEST_DIR), prompty.userdir.SKEL_DIR, "default.prompty") - c.loadPromptFile() - self.assertGreater(len(c.promptString), 0) + c.prompt_file = os.path.join(os.path.dirname(TEST_DIR), prompty.userdir.SKEL_DIR, "default.prompty") + c.load_prompt_file() + self.assertGreater(len(c.prompt_string), 0) diff --git a/test/test_skel.py b/test/test_skel.py index dc5b4ee..772e177 100644 --- a/test/test_skel.py +++ b/test/test_skel.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals from test import prompty from test import UnitTestWrapper @@ -21,9 +20,9 @@ def test_skelFiles(self): files = [os.path.join(test_dir, "..", "skel", f) for f in all_files if f.endswith(".prompty")] for file in files: p = prompty.prompt.Prompt(prompty.status.Status()) - p.config.promptFile = file - p.config.loadPromptFile() - s = p.getPrompt() + p.config.prompt_file = file + p.config.load_prompt_file() + s = p.get_prompt() print("Testing " + file) print(s.replace(prompty.colours.Colours.NOCOUNT_START, '').replace(prompty.colours.Colours.NOCOUNT_END, '')) print() diff --git a/test/test_vcs.py b/test/test_vcs.py index f989b01..a3a1a7f 100644 --- a/test/test_vcs.py +++ b/test/test_vcs.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from __future__ import unicode_literals import socket import getpass @@ -56,7 +55,7 @@ def test_gitUnavailable(self, mock_sp): g = prompty.git.Git(prompty.status.Status(0)) self.assertEqual(False, g.installed) - self.assertEqual(False, g.isRepo) + self.assertEqual(False, g.is_repo) self.assertEqual("", g.branch) self.assertEqual(0, g.changed) self.assertEqual(0, g.staged) @@ -82,7 +81,7 @@ def test_cleanRepo(self, mock_sp): g = prompty.git.Git(prompty.status.Status(0)) self.assertEqual(True, g.installed) - self.assertEqual(True, g.isRepo) + self.assertEqual(True, g.is_repo) self.assertEqual("develop", g.branch) self.assertEqual(0, g.changed) self.assertEqual(0, g.staged) @@ -114,7 +113,7 @@ def test_dirtyRepo(self, mock_sp): g = prompty.git.Git(prompty.status.Status(0)) self.assertEqual(True, g.installed) - self.assertEqual(True, g.isRepo) + self.assertEqual(True, g.is_repo) self.assertEqual("master", g.branch) self.assertEqual(1, g.changed) self.assertEqual(2, g.staged) @@ -143,7 +142,7 @@ def test_notARepo(self, mock_sp): g = prompty.git.Git(prompty.status.Status(0)) self.assertEqual(True, g.installed) - self.assertEqual(False, g.isRepo) + self.assertEqual(False, g.is_repo) self.assertEqual("", g.branch) self.assertEqual(0, g.changed) self.assertEqual(0, g.staged) @@ -191,7 +190,7 @@ def test_svnUnavailable(self, mock_sp): g = prompty.svn.Subversion(prompty.status.Status(0)) self.assertEqual(False, g.installed) - self.assertEqual(False, g.isRepo) + self.assertEqual(False, g.is_repo) self.assertEqual("", g.branch) self.assertEqual(0, g.changed) self.assertEqual(0, g.untracked) @@ -209,7 +208,7 @@ def test_notARepo(self, mock_sp): g = prompty.svn.Subversion(prompty.status.Status(0)) self.assertEqual(True, g.installed) - self.assertEqual(False, g.isRepo) + self.assertEqual(False, g.is_repo) self.assertEqual("", g.branch) self.assertEqual(0, g.changed) self.assertEqual(0, g.staged) @@ -252,7 +251,7 @@ def test_info(self, mock_sp): g = prompty.svn.Subversion(prompty.status.Status(0)) self.assertEqual(True, g.installed) - self.assertEqual(True, g.isRepo) + self.assertEqual(True, g.is_repo) self.assertEqual("trunk", g.branch) @mock.patch('prompty.vcs.subprocess')