Last active
May 28, 2020 11:28
-
-
Save ptoche/903d7f37f67c228abf9c2c8c157f816a to your computer and use it in GitHub Desktop.
Spyder hacks: backup and edit config files for each environment (Upcoming release Spyder 5 will hopefully offer something better than this soon)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Spyder hacks: backup and edit config files for each environment. | |
Upcoming release Spyder 5 will hopefully offer something better than this soon. | |
Created 28 May 2020 | |
@author: Patrick Toche | |
""" | |
# import common packages here, otherwise import inside each function (helps me understand which packages are needed where and how much they are needed) | |
import os | |
def make_ini_names(filename=None, copyname=None, verbose=False): | |
""" | |
Set names for the original and backup ini files. | |
The default ini file is at `.spyder-py3/config/spyder.ini`. | |
The default bak file is at `.spyder-py3/config/backups/spyder_ini_%Y_%m_%d_%H_%M.bak`. | |
""" | |
from datetime import datetime | |
if not filename: | |
filename = os.path.join(os.path.expanduser('~'), '.spyder-py3/config/spyder.ini') | |
filepath = os.path.dirname(filename) | |
dirs = os.path.normpath(filepath).split(os.sep)[:-1] | |
dirs.append('backups') | |
copypath = os.sep.join(dirs) | |
if not copyname: | |
copyname = datetime.now().strftime(os.path.join(copypath, 'backups/spyder_ini_%Y_%m_%d_%H_%M.bak')) | |
return [filename, copyname] | |
def backup_ini(filename=None, copyname=None, verbose=True): | |
""" | |
Make a copy of the Spyder ini file for backup. | |
Both the source path (argument `filename`) and destination path (argument `copyname`) may be changed. | |
""" | |
from shutil import copy2 # shutil.copy2 copies metadata+permissions | |
# set the file names, if not provided by user: | |
if not filename: | |
filename = make_ini_names(filename=None, copyname=None, verbose=False)[0] | |
# set the initiation file backup name, if not provided by user: | |
if not copyname: | |
copyname = make_ini_names(filename=None, copyname=None, verbose=False)[1] | |
copy2(filename, copyname) | |
if verbose: | |
print('A copy of your ini file was saved as\n', copyname) | |
return None | |
def edit_ini_keys(config, verbose=False): | |
""" | |
First remove existing shortcuts, then set new ones. | |
This is done by editing the config list. | |
This function does not allow user input: These edits are my own preferences! | |
To edit shortcuts (key bindings), edit this function. | |
Many more examples of actions assigned to keys may be found in the ini file. | |
""" | |
try: | |
# Key F12 | |
# Default F12: 'editor/breakpoint' | |
# Default editor/run selection: 'F9' | |
config['shortcuts']['editor/breakpoint'] = 'Shift+Return' | |
config['shortcuts']['editor/run selection'] = 'F12' | |
# Recycling value from editor/run cell and advance (was 'Shift+Return') | |
# Key F11 | |
# Default F11: '_/fullscreen mode' | |
# MacOS Catalina: Keyboard -> Shortcuts -> Mission Control -> Show Desktop = F11 | |
# Default editor/run cell: 'Meta+Return' | |
config['shortcuts']['_/fullscreen mode'] = 'Meta+Return' | |
config['shortcuts']['editor/run cell'] = 'F11' | |
# Swapping values | |
# Key F10 | |
# Default F10: 'profiler/run profiler' | |
# Default editor/re-run last cell: 'Alt+Return' | |
config['shortcuts']['profiler/run profiler'] = 'Alt+Return' | |
config['shortcuts']['editor/re-run last cell'] = 'F10' | |
# Swapping values | |
# Key F9 | |
# Default F9: 'editor/run selection' | |
# Default editor/run cell and advance: 'Shift+Return' | |
config['shortcuts']['editor/run cell and advance'] = 'F9' | |
# 'editor/run selection' already set above | |
# Key F8 | |
# Default F8: 'pylint/run analysis' | |
# Keeping default value | |
# Key F7 | |
# Default F7: '' | |
# Keeping default value (unassigned) | |
# Key F6 | |
# Default F6: '_/re-run last script' | |
# Keeping default value | |
# Key F5 | |
# Default F5: '_/run' | |
# Keeping default value | |
# Key F4 | |
# Default F4: '' | |
# Keeping default value (unassigned) | |
# Key F3 | |
# Default F3: 'find_replace/find next' | |
# Keeping default value | |
# Key F2 | |
# Default F2: '' | |
# Keeping default value (unassigned) | |
# Key F1 | |
# Default F1: '_/spyder documentation' | |
# Keeping default value | |
# Key Ctrl+Right | |
# Default Ctrl+Right: 'editor/next word' | |
# Default editor/end of line: 'Meta+E' | |
config['shortcuts']['editor/next word'] = 'Meta+E' | |
config['shortcuts']['editor/end of line'] = 'Ctrl+Right' | |
# Swapping values | |
# Key Ctrl+Left | |
# Default Ctrl+Left: 'editor/previous word' | |
# Default editor/start of line: 'Meta+A' | |
config['shortcuts']['editor/previous word'] = 'Meta+A' | |
config['shortcuts']['editor/start of line'] = 'Ctrl+Left' | |
# Swapping values | |
except Exception as e: | |
print('A problem occurred while attempting to set key bindings. Error message:\n', e) | |
return config | |
def edit_spyder_ini(filename=None, copyname=None, verbose=False, theme='spyder/dark'): | |
""" | |
Automate preference settings by editing the config file. | |
""" | |
import configparser | |
# set the file names, if not provided by user: | |
if not filename: | |
filename = make_ini_names(filename=None, copyname=None, verbose=False)[0] | |
# set the initiation file backup name, if not provided by user: | |
if not copyname: | |
copyname = make_ini_names(filename=None, copyname=None, verbose=False)[1] | |
# setup the parser: | |
config = configparser.ConfigParser() | |
config.read(filename) | |
# edit shortcuts: | |
edit_ini_keys(config, verbose=verbose) | |
# Set a theme (Default: 'spyder/dark') | |
config['appearance']['selected'] = theme | |
# Save a backup of the ini file | |
backup_ini(filename=filename, copyname=copyname, verbose=False) | |
# Save the edited ini file | |
with open(filename, 'w') as f: | |
config.write(f) | |
if verbose: | |
print('Your Spyder ini file has been modified. A backup copy was saved.\ | |
\nTo revert changes, delete the original ini file and replace it with the backup.') | |
print('The original ini file was ', filename) | |
print('The backup ini file was saved as ', copyname) | |
return None | |
def make_temp_file(envname=None, filename=None): | |
""" | |
Create a temp file for a named environment inside a subdirectory. | |
If the file already exists, load it (I like to keep/edit the temp files across sessions). | |
""" | |
from pathlib import Path # to create a directory if needed | |
from spyder.utils import encoding # not needed, but more general | |
from spyder.py3compat import to_text_string # not needed, but more general | |
from IPython import get_ipython # to load a new instance of a file in the editor | |
if not filename: | |
filename = os.path.join(os.path.expanduser('~'), '.spyder-py3/temp.py') | |
# if no environment is supplied, use `temp.py` in `root` directory, | |
root = os.path.dirname(filename) | |
# otherwise create a `temp.py` inside an `<envname>` directory | |
if not envname: | |
envname ='' | |
envdir = root | |
editor_location = "It resides in the <root> environment." | |
else: | |
# if directory `<envname>` does not exist, create it: | |
envdir = os.path.join(root, 'envs', envname) | |
Path(envdir).mkdir(parents=True, exist_ok=True) | |
editor_location = "It resides in the <"+envname+"> environment." | |
tempfile = os.path.join(envdir, os.path.basename(filename)) | |
# if `temp.py` file does not exist, create it | |
if not os.path.isfile(tempfile): | |
# Creating temporary file | |
default = ['# -*- coding: utf-8 -*-', | |
'"""', '' "Spyder Editor", '', | |
"This is a temporary script file", '', | |
editor_location, '', | |
'"""', '', ''] | |
text = os.linesep.join([encoding.to_unicode(qstr) | |
for qstr in default]) | |
encoding.write(to_text_string(text), tempfile, 'utf-8') | |
# now load the new temp file | |
ipython = get_ipython() | |
ipython.magic(f"%edit {tempfile}") | |
return None | |
# BASIC TESTS: | |
# Backup Spyder's ini file before editing | |
backup_ini() | |
# Edit Spyder's ini file (a backup is automatically created) | |
edit_spyder_ini() | |
# Create an environment-specific temp file | |
make_temp_file() | |
make_temp_file('myenv') | |
# FROM HERE ON IT'S WORK IN PROGRESS! | |
# TO DO: get spyder to load ini file while running | |
def get_code(code): | |
""" | |
borrowed from https://github.com/vatlab/sos-notebook | |
""" | |
if code.startswith('%') or code.startswith('!'): | |
lines = re.split(r'(?<!\\)\n', code, 1) | |
# remove lines joint by \ | |
lines[0] = lines[0].replace('\\\n', '') | |
else: | |
lines = code.split('\n', 1) | |
pieces = self._interpolate_text( | |
lines[0], quiet=False).strip().split(None, 1) | |
if len(pieces) == 2: | |
command_line = pieces[1] | |
else: | |
command_line = '' | |
remaining_code = lines[1] if len(lines) > 1 else '' | |
return command_line, remaining_code | |
def reset_ini(filename, options): | |
""" | |
logic borrowed from: https://github.com/vatlab/sos/blob/master/misc/spyder/spyder_kernel.py | |
""" | |
import shlex | |
import subprocess | |
import sys | |
import argparse | |
if not filename: | |
filename = make_ini_names(filename=None, copyname=None, verbose=False)[0] | |
parser = argparse.ArgumentParser(prog='%edit', | |
description='load a Spyder ini file in an open session') | |
parser.add_argument('filenames', nargs='+') | |
parser.add_argument('-c', '--cd', action='store_true', dest='spyder_hack') | |
TO DO : FIGURE OUT WHAT SELF IS AND HOW TO PARSE OPTIONS... | |
options, remaining_code = self.get_code(code) | |
try: | |
args = parser.parse_args(shlex.split(options)) | |
except SystemExit: | |
return | |
args.filenames = [os.path.expanduser(x) for x in args.filenames] | |
import1 = "import sys" | |
import2 = "from spyder.app.start import send_args_to_spyder" | |
code = "send_args_to_spyder([{}])".format(','.join('"{}"'.format(x) for x in args.filenames)) | |
cmd = "{0} -c '{1}; {2}; {3}'".format(sys.executable, | |
import1, import2, code) | |
subprocess.call(cmd, shell=True) | |
return None | |
reset(ini_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment