Skip to content
Snippets Groups Projects
Commit 5a0f8fb9 authored by nimrod's avatar nimrod
Browse files

Python startup script.

Use the pythonrc Python startup script added as a submodule instead of
fiddling around with a script to build a (not really working) version
for myself.
parent 347014cd
Branches
No related tags found
No related merge requests found
...@@ -16,7 +16,7 @@ export PATH="$HOME/Documents/Shore/ssl-ca:$PATH" ...@@ -16,7 +16,7 @@ export PATH="$HOME/Documents/Shore/ssl-ca:$PATH"
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
export PATH="$HOME/.cabal/bin:$PATH" export PATH="$HOME/.cabal/bin:$PATH"
export PATH="$HOME/Documents/bin:$PATH" export PATH="$HOME/Documents/bin:$PATH"
export PYTHONSTARTUP=~/.config/python/startup.py export PYTHONSTARTUP=~/.config/pythonrc/pythonrc.py
export AWS_DEFAULT_PROFILE='shore' export AWS_DEFAULT_PROFILE='shore'
export ANSIBLE_VERBOSITY=2 export ANSIBLE_VERBOSITY=2
export ANSIBLE_COMMAND_WARNINGS=True export ANSIBLE_COMMAND_WARNINGS=True
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# The MIT License (MIT)
#
# Copyright (c) 2015-2017 Steven Fernandez
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""pymp - lonetwin's pimped-up pythonrc
This file will be executed when the Python interactive shell is started, if
$PYTHONSTARTUP is in your environment and points to this file. You could
also make this file executable and call it directly.
This file creates an InteractiveConsole instance, which provides:
* execution history
* colored prompts and pretty printing
* auto-indentation
* intelligent tab completion:¹
* source code listing for objects
* session history editing using your $EDITOR, as well as editing of
source files for objects or regular files
* temporary escape to $SHELL or ability to execute a shell command and
capturing the result into the '_' variable
* convenient printing of doc stings and search for entries in online docs
* auto-execution of a virtual env specific (`.venv_rc.py`) file at startup
If you have any other good ideas please feel free to submit issues/pull requests.
¹ Since python 3.4 the default interpreter also has tab completion
enabled however it does not do pathname completion
"""
# Fix for Issue #5
# - Exit if being called from within ipython
try:
import sys
__IPYTHON__ and sys.exit(0)
except NameError:
pass
try:
import builtins
except ImportError:
import __builtin__ as builtins
import atexit
import glob
import inspect
import keyword
import os
import pkgutil
import pprint
import re
import readline
import rlcompleter
import shlex
import signal
import subprocess
import webbrowser
from code import InteractiveConsole
from collections import namedtuple
from functools import partial
from tempfile import NamedTemporaryFile
__version__ = "0.6.4"
config = dict(
HISTFILE = os.path.expanduser("~/.python_history"),
HISTSIZE = -1,
EDITOR = os.getenv('EDITOR', 'vi'),
SHELL = os.getenv('SHELL', '/bin/bash'),
EDIT_CMD = '\e',
SH_EXEC = '!',
DOC_CMD = '?',
DOC_URL = "https://docs.python.org/{sys.version_info.major}/search.html?q={term}",
HELP_CMD = '\h',
LIST_CMD = '\l',
VENV_RC = ".venv_rc.py"
)
class ImprovedConsole(InteractiveConsole, object):
"""
Welcome to lonetwin's pimped up python prompt
You've got color, tab completion, auto-indentation, pretty-printing
and more !
* A tab with preceding text will attempt auto-completion of
keywords, names in the current namespace, attributes and methods.
If the preceding text has a '/', filename completion will be
attempted. Without preceding text four spaces will be inserted.
* History will be saved in {HISTFILE} when you exit.
* If you create a file named {VENV_RC} in the current directory, the
contents will be executed in this session before the prompt is
shown.
* Typing out a defined name followed by a '{DOC_CMD}' will print out
the object's __doc__ attribute if one exists.
(eg: []? / str? / os.getcwd? )
* Typing '{DOC_CMD}{DOC_CMD}' after something will search for the
term at {DOC_URL}
(eg: try webbrowser.open??)
* Open the your editor with current session history, source code of
objects or arbitrary files, using the '{EDIT_CMD}' command.
* List source code for objects using the '{LIST_CMD}' command.
* Execute shell commands using the '{SH_EXEC}' command.
Try `<cmd> -h` for any of the commands to learn more.
The EDITOR, SHELL, command names and more can be changed in the
config dict at the top of this file. Make this your own !
"""
def __init__(self, tab=' ', *args, **kwargs):
self.session_history = [] # This holds the last executed statements
self.buffer = [] # This holds the statement to be executed
self.tab = tab
self._indent = ''
super(ImprovedConsole, self).__init__(*args, **kwargs)
self.init_color_functions()
self.init_readline()
self.init_prompt()
self.init_pprint()
def init_color_functions(self):
"""Populates globals dict with some helper functions for colorizing text
"""
def colorize(color_code, text, bold=True, readline_workaround=False):
reset = '\033[0m'
color = '\033[{0}{1}m'.format('1;' if bold else '', color_code)
# - reason for readline_workaround: http://bugs.python.org/issue20359
if readline_workaround:
color = '\001{color}\002'.format(color=color)
reset = '\001{reset}\002'.format(reset=reset)
return "{color}{text}{reset}".format(**vars())
g = globals()
for code, color in enumerate(['red', 'green', 'yellow', 'blue', 'purple', 'cyan'], 31):
g[color] = partial(colorize, code)
def init_readline(self):
"""Activates history and tab completion
"""
# - mainly borrowed from site.enablerlcompleter() from py3.4+
# Reading the initialization (config) file may not be enough to set a
# completion key, so we set one first and then read the file.
readline_doc = getattr(readline, '__doc__', '')
if readline_doc is not None and 'libedit' in readline_doc:
readline.parse_and_bind('bind ^I rl_complete')
else:
readline.parse_and_bind('tab: complete')
try:
readline.read_init_file()
except OSError:
# An OSError here could have many causes, but the most likely one
# is that there's no .inputrc file (or .editrc file in the case of
# Mac OS X + libedit) in the expected location. In that case, we
# want to ignore the exception.
pass
if readline.get_current_history_length() == 0:
# If no history was loaded, default to .python_history.
# The guard is necessary to avoid doubling history size at
# each interpreter exit when readline was already configured
# see: http://bugs.python.org/issue5845#msg198636
try:
readline.read_history_file(config['HISTFILE'])
except IOError:
pass
atexit.register(readline.write_history_file,
config['HISTFILE'])
readline.set_history_length(config['HISTSIZE'])
# - replace default completer
readline.set_completer(self.improved_rlcompleter())
# - enable auto-indenting
readline.set_pre_input_hook(self.auto_indent_hook)
def init_prompt(self):
"""Activates color on the prompt based on python version.
Also adds the hosts IP if running on a remote host over a
ssh connection.
"""
prompt_color = green if sys.version_info.major == 2 else yellow
sys.ps1 = prompt_color('>>> ', readline_workaround=True)
sys.ps2 = red('... ', readline_workaround=True)
# - if we are over a remote connection, modify the ps1
if os.getenv('SSH_CONNECTION'):
_, _, this_host, _ = os.getenv('SSH_CONNECTION').split()
sys.ps1 = prompt_color('[{}]>>> '.format(this_host), readline_workaround=True)
sys.ps2 = red('[{}]... '.format(this_host), readline_workaround=True)
def init_pprint(self):
"""Activates pretty-printing of output values.
"""
keys_re = re.compile(r'([\'\("]+(.*?[\'\)"]: ))+?')
color_dict = partial(keys_re.sub, lambda m: purple(m.group()))
format_func = pprint.pformat
if sys.version_info.major > 3 and sys.version.minor > 3:
format_func = partial(pprint.pformat, compact=True)
def pprint_callback(value):
if value is not None:
try:
rows, cols = os.get_teminal_size()
except AttributeError:
try:
rows, cols = map(int, subprocess.check_output(['stty', 'size']).split())
except:
cols = 80
builtins._ = value
formatted = format_func(value, width=cols)
print(color_dict(formatted) if issubclass(type(value), dict) else blue(formatted))
sys.displayhook = pprint_callback
def improved_rlcompleter(self):
"""Enhances the default rlcompleter
The function enhances the default rlcompleter by also doing
pathname completion and module name completion for import
statements. Additionally, it inserts a tab instead of attempting
completion if there is no preceding text.
"""
completer = rlcompleter.Completer(namespace=self.locals)
# - remove / from the delimiters to help identify possibility for path completion
readline.set_completer_delims(readline.get_completer_delims().replace('/', ''))
modlist = frozenset(name for _, name, _ in pkgutil.iter_modules())
def complete_wrapper(text, state):
line = readline.get_line_buffer().strip()
if line == '':
return None if state > 0 else self.tab
if state == 0:
if line.startswith('import') or line.startswith('from'):
completer.matches = [name for name in modlist if name.startswith(text)]
else:
match = completer.complete(text, state)
if match is None and '/' in text:
completer.matches = glob.glob(text+'*')
try:
match = completer.matches[state]
return '{}{}'.format(match, ' ' if keyword.iskeyword(match) else '')
except IndexError:
return None
return complete_wrapper
def auto_indent_hook(self):
"""Hook called by readline between printing the prompt and
starting to read input.
"""
readline.insert_text(self._indent)
readline.redisplay()
def raw_input(self, *args):
"""Read the input and delegate if necessary.
"""
line = InteractiveConsole.raw_input(self, *args)
if line == config['HELP_CMD']:
print(cyan(self.__doc__).format(**config))
line = ''
elif line.startswith(config['EDIT_CMD']):
offset = len(config['EDIT_CMD'])
line = self.process_edit_cmd(line[offset:].strip())
elif line.startswith(config['SH_EXEC']):
offset = len(config['SH_EXEC'])
line = self.process_sh_cmd(line[offset:].strip())
elif line.startswith(config['LIST_CMD']):
# - strip off the possible tab-completed '('
line = line.rstrip('(')
offset = len(config['LIST_CMD'])
line = self.process_list_cmd(line[offset:].strip())
elif line.endswith(config['DOC_CMD']):
if line.endswith(config['DOC_CMD']*2):
# search for line in online docs
# - strip off the '??' and the possible tab-completed
# '(' or '.' and replace inner '.' with '+' to create the
# query search string
line = line.rstrip(config['DOC_CMD'] + '.(').replace('.', '+')
webbrowser.open(config['DOC_URL'].format(sys=sys, term=line))
line = ''
else:
line = line.rstrip(config['DOC_CMD'] + '.(')
if not line:
line = 'dir()'
elif keyword.iskeyword(line):
line = 'help("{}")'.format(line)
else:
line = 'print({}.__doc__)'.format(line)
elif line.startswith(self.tab) or self._indent:
if line.strip():
# if non empty line with an indent, check if the indent
# level has been changed
leading_space = line[:line.index(line.lstrip()[0])]
if self._indent != leading_space:
# indent level changed, update self._indent
self._indent = leading_space
else:
# - empty line, decrease indent
self._indent = self._indent[:-len(self.tab)]
line = self._indent
elif line.startswith('%'):
self.writeline('Y U NO LIKE ME?')
return line
return line or ''
def push(self, line):
"""Wrapper around InteractiveConsole's push method for adding an
indent on start of a block.
"""
more = super(ImprovedConsole, self).push(line)
if more:
if line[-1] in (":", '[', '{', '('):
self._indent += self.tab
else:
self._indent = ''
return more
def write(self, data):
"""Write out data to stderr
"""
sys.stderr.write(red(data))
def writeline(self, data):
"""Same as write but adds a newline to the end
"""
return self.write('{}\n'.format(data))
def resetbuffer(self):
self._indent = ''
previous = ''
for line in self.buffer:
# - replace multiple empty lines with one before writing to session history
stripped = line.strip()
if stripped or stripped != previous:
self.session_history.append(line)
previous = stripped
return super(ImprovedConsole, self).resetbuffer()
def _doc_to_usage(method):
def inner(self, arg):
arg = arg.strip()
if arg.startswith('-h') or arg.startswith('--help'):
return self.writeline(blue(method.__doc__.strip().format(**config)))
return method(self, arg)
return inner
def _mktemp_buffer(self, lines):
"""Writes lines to a temp file and returns the filename.
"""
with NamedTemporaryFile(mode='w+', suffix='.py', delete=False) as tempbuf:
tempbuf.write('\n'.join(lines))
return tempbuf.name
def _exec_from_file(self, filename, quiet=False):
previous = ''
for stmt in open(filename):
# - skip over multiple empty lines
stripped = stmt.strip()
if stripped == '' and stripped == previous:
continue
if not quiet:
self.write(cyan("... {}".format(stmt)))
if not stripped.startswith('#'):
line = stmt.strip('\n')
self.push(line)
readline.add_history(line)
previous = stripped
def lookup(self, name, namespace=None):
"""Lookup the (dotted) object specified with the string `name`
in the specified namespace or in the current namespace if
unspecified.
"""
components = name.split('.', 1)
name = components.pop(0)
obj = getattr(namespace, name, namespace) if namespace else self.locals.get(name)
return self.lookup(components[0], obj) if components else obj
@_doc_to_usage
def process_edit_cmd(self, arg=''):
"""{EDIT_CMD} [object|filename]
Open {EDITOR} with session history, provided filename or
object's source file.
- without arguments, a temporary file containing session history is
created and opened in {EDITOR}. On quitting the editor, all
the non commented lines in the file are executed.
- with a filename argument, the file is opened in the editor. On
close, you are returned bay to the interpreter.
- with an object name argument, an attempt is made to lookup the
source file of the object and it is opened if found. Else the
argument is treated as a filename.
"""
if arg:
obj = self.lookup(arg)
try:
filename = inspect.getsourcefile(obj) if obj else arg
except (IOError, TypeError, NameError) as e:
return self.writeline(e)
else:
# - make a list of all lines in session history, commenting
# any non-blank lines.
filename = self._mktemp_buffer("# {}".format(line) if line else ''
for line in (line.strip('\n') for line in self.session_history))
# - shell out to the editor
os.system('{} {}'.format(config['EDITOR'], filename))
# - if arg was not provided (we edited session history), execute
# it in the current namespace
if not arg:
self._exec_from_file(filename)
os.unlink(filename)
@_doc_to_usage
def process_sh_cmd(self, cmd):
"""{SH_EXEC} [cmd [args ...] | {{fmt string}}]
Escape to {SHELL} or execute `cmd` in {SHELL}
- without arguments, the current interpreter will be suspended
and you will be dropped in a {SHELL} prompt. Use fg to return.
- with arguments, the text will be executed in {SHELL} and the
output/error will be displayed. Additionally '_' will contain
a named tuple with the (<stdout>, <stderror>, <return_code>)
for the execution of the command.
You may pass strings from the global namespace to the command
line using the `.format()` syntax. for example:
>>> filename = '/does/not/exist'
>>> !ls {{filename}}
ls: cannot access /does/not/exist: No such file or directory
>>> _
CmdExec(out='', err='ls: cannot access /does/not/exist: No such file or directory\n', rc=2)
"""
if cmd:
try:
cmd = cmd.format(**self.locals)
cmd = shlex.split(cmd)
if cmd[0] == 'cd':
os.chdir(os.path.expanduser(os.path.expandvars(' '.join(cmd[1:]) or '${HOME}')))
else:
cmd_exec = namedtuple('CmdExec', ['out', 'err', 'rc'])
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = process.communicate()
rc = process.returncode
print (red(err.decode('utf-8')) if err else green(out.decode('utf-8'), bold=False))
builtins._ = cmd_exec(out, err, rc)
del cmd_exec
except:
self.showtraceback()
else:
if os.getenv('SSH_CONNECTION'):
# I use the bash function similar to the one below in my
# .bashrc to directly open a python prompt on remote
# systems I log on to.
# function rpython { ssh -t $1 -- "python" }
# Unfortunately, suspending this ssh session, does not place me
# in a shell, so I need to create one:
os.system(config['SHELL'])
else:
os.kill(os.getpid(), signal.SIGSTOP)
@_doc_to_usage
def process_list_cmd(self, arg):
"""
{LIST_CMD} <object> - List source code for object, if possible.
"""
try:
if not arg:
self.writeline('source list command requires an argument '
'(eg: {} foo)\n'.format(config['LIST_CMD']))
src_lines, offset = inspect.getsourcelines(self.lookup(arg))
except (IOError, TypeError, NameError) as e:
self.writeline(e)
else:
for line_no, line in enumerate(src_lines, offset+1):
self.write(cyan("{0:03d}: {1}".format(line_no, line)))
def interact(self):
"""A forgiving wrapper around InteractiveConsole.interact()
"""
venv_rc_done = '(no venv rc found)'
try:
self._exec_from_file(config['VENV_RC'], quiet=True)
venv_rc_done = green('Successfully executed venv rc !')
except IOError:
pass
banner = ("Welcome to the ImprovedConsole (version {version})\n"
"Type in {HELP_CMD} for list of features.\n"
"{venv_rc_done}").format(
version=__version__, venv_rc_done=venv_rc_done, **config)
retries = 2
while retries:
try:
super(ImprovedConsole, self).interact(banner=banner)
except SystemExit:
# Fixes #2: exit when 'quit()' invoked
break
except:
import traceback
retries -= 1
print(red("I'm sorry, ImprovedConsole could not handle that !\n"
"Please report an error with this traceback, "
"I would really appreciate that !"))
traceback.print_exc()
print(red("I shall try to restore the crashed session.\n"
"If the crash occurs again, please exit the session"))
banner = blue("Your crashed session has been restored")
else:
# exit with a Ctrl-D
break
# Exit the Python shell on exiting the InteractiveConsole
sys.exit()
if not os.getenv('SKIP_PYMP'):
# - create our pimped out console and fire it up !
pymp = ImprovedConsole()
pymp.interact()
#!/usr/bin/env python
from pprint import PrettyPrinter
pprint = PrettyPrinter().pprint
Subproject commit d9f2c7a9caf0076c011bbe0aa42fca641432ea57
...@@ -9,8 +9,6 @@ echo Loading dconf config >> /dev/stderr ...@@ -9,8 +9,6 @@ echo Loading dconf config >> /dev/stderr
Documents/bin/dconf-load Documents/bin/dconf-load
echo Configuring Git repo >> /dev/stderr echo Configuring Git repo >> /dev/stderr
git config --local status.showUntrackedFiles no git config --local status.showUntrackedFiles no
echo Creating Python startup file >> /dev/stderr
Documents/bin/gen-python-startup
echo Creating Bash completion scripts >> /dev/stderr echo Creating Bash completion scripts >> /dev/stderr
Documents/bin/gen-bash-completion Documents/bin/gen-bash-completion
echo Adding Cron job >> /dev/stderr echo Adding Cron job >> /dev/stderr
......
...@@ -21,3 +21,6 @@ ...@@ -21,3 +21,6 @@
[submodule "Documents/Shore/poquita"] [submodule "Documents/Shore/poquita"]
path = Documents/Shore/poquita path = Documents/Shore/poquita
url = https://www.shore.co.il/git/poquita url = https://www.shore.co.il/git/poquita
[submodule ".config/pythonrc"]
path = .config/pythonrc
url = https://github.com/lonetwin/pythonrc.git
#!/bin/sh
set -eu
find "$HOME/.config/python/startup" -type f \! -name '.*' -print0 | sort --zero | xargs -0 cat > "$HOME/.config/python/startup.py"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment