Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom code directory #179

Merged
merged 12 commits into from
Nov 6, 2016
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Carlos Pereira Atencio ([email protected])
Nick Sarbicki ([email protected])
Kushal Das ([email protected])
Tibs / Tony Ibbs ([email protected])
Zander Brown
56 changes: 43 additions & 13 deletions mu/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@
MICROBIT_VID = 3368
#: The user's home directory.
HOME_DIRECTORY = os.path.expanduser('~')
#: The default directory for Python scripts. This needs to be in the user's
# home directory, and visible (so not a . directory)
PYTHON_DIRECTORY = os.path.join(HOME_DIRECTORY, 'mu_code')
# Name of the directory within the home folder to use by default
WORKSPACE_NAME = 'mu_code'
#: The default directory for application data (i.e., configuration).
DATA_DIR = appdirs.user_data_dir(appname='mu', appauthor='python')
#: The default directory for application logs.
Expand Down Expand Up @@ -112,12 +111,42 @@ def get_settings_path():
if not os.path.exists(settings_dir):
settings_dir = os.path.join(DATA_DIR, settings_filename)
if not os.path.exists(settings_dir):
with open(settings_dir, 'w') as f:
logger.debug('Creating settings file: {}'.format(settings_dir))
json.dump({}, f)
try:
with open(settings_dir, 'w') as f:
logger.debug('Creating settings file: {}'.format(
settings_dir))
json.dump({}, f)
except FileNotFoundError:
logger.error('Unable to create settings file: {}'.format(
settings_dir))
return settings_dir


def get_workspace_dir():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admittedly tests for this would be good.
Any suggestions of what & how?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Briefly (I have dad duty things taking up my time this evening, will reply more fully tomorrow AM), yes: ensure you set up the test with a settings file somewhere temporary and Mock away get_settings_path to return a path to this new temp settings file. Then test to your hearts' content. Does this make sense..?

Copy link
Contributor Author

@ZanderBrown ZanderBrown Oct 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. unfortunately i may not be able to work on this again until tomorrow PM

"""
The default is to use a directory in the users home folder however
in some network systems this in inaccessable. This allows a key in the
settings file to be used to set a custom path
"""
settings_path = get_settings_path()
try:
with open(settings_path) as f:
try:
# Load up the JSON
sets = json.load(f)
if 'workspace' in sets:
# The key exists so use it
return sets['workspace']
except ValueError:
logger.error('Settings file {} could not be parsed.'.format(
settings_path))
except FileNotFoundError:
logger.error('Settings file {} does not exist.'.format(
settings_path))
# If all else fails return the default directory
return os.path.join(HOME_DIRECTORY, WORKSPACE_NAME)


def check_flake(filename, code):
"""
Given a filename and some code to be checked, uses the PyFlakesmodule to
Expand Down Expand Up @@ -290,12 +319,12 @@ def __init__(self, view):
self.fs = None
self.theme = 'day'
self.user_defined_microbit_path = None
if not os.path.exists(PYTHON_DIRECTORY):
logger.debug('Creating directory: {}'.format(PYTHON_DIRECTORY))
os.makedirs(PYTHON_DIRECTORY)
if not os.path.exists(DATA_DIR):
logger.debug('Creating directory: {}'.format(DATA_DIR))
os.makedirs(DATA_DIR)
if not os.path.exists(get_workspace_dir()):
logger.debug('Creating directory: {}'.format(get_workspace_dir()))
os.makedirs(get_workspace_dir())

def restore_session(self):
"""
Expand Down Expand Up @@ -404,7 +433,7 @@ def add_fs(self):
if self.fs is None:
try:
microfs.get_serial()
self._view.add_filesystem(home=PYTHON_DIRECTORY)
self._view.add_filesystem(home=get_workspace_dir())
self.fs = True
except IOError:
message = 'Could not find an attached BBC micro:bit.'
Expand Down Expand Up @@ -525,7 +554,7 @@ def load(self):
Loads a Python file from the file system or extracts a Python sccript
from a hex file.
"""
path = self._view.get_load_path(PYTHON_DIRECTORY)
path = self._view.get_load_path(get_workspace_dir())
logger.info('Loading script from: {}'.format(path))
try:
if path.endswith('.py'):
Expand Down Expand Up @@ -557,7 +586,7 @@ def save(self):
return
if tab.path is None:
# Unsaved file.
tab.path = self._view.get_save_path(PYTHON_DIRECTORY)
tab.path = self._view.get_save_path(get_workspace_dir())
if tab.path:
# The user specified a path to a file.
if not os.path.basename(tab.path).endswith('.py'):
Expand Down Expand Up @@ -631,7 +660,8 @@ def quit(self, *args, **kwargs):
paths.append(widget.path)
session = {
'theme': self.theme,
'paths': paths
'paths': paths,
'workspace': get_workspace_dir()
}
logger.debug(session)
settings_path = get_settings_path()
Expand Down
2 changes: 1 addition & 1 deletion tests/settings.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"theme": "night", "paths": ["path/foo.py", "path/bar.py"]}
{"theme": "night", "paths": ["path/foo.py", "path/bar.py"], "workspace": "/home/foo/mycode"}
1 change: 1 addition & 0 deletions tests/settingswithoutworkspace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"theme": "night", "paths": ["path/foo.py", "path/bar.py"]}
35 changes: 26 additions & 9 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def test_CONSTANTS():
Ensure the expected constants exist.
"""
assert mu.logic.HOME_DIRECTORY
assert mu.logic.PYTHON_DIRECTORY
assert mu.logic.DATA_DIR
assert mu.logic.WORKSPACE_NAME
# These should NEVER change.
assert mu.logic.MICROBIT_PID == 516
assert mu.logic.MICROBIT_VID == 3368
Expand Down Expand Up @@ -152,6 +152,21 @@ def test_get_settings_no_files():
assert mock_json_dump.call_count == 1


def test_get_workspace():
"""
Normally return generated folder otherwise user value
"""
should_be = os.path.join(mu.logic.HOME_DIRECTORY,
mu.logic.WORKSPACE_NAME)
with mock.patch('mu.logic.get_settings_path',
return_value='tests/settingswithoutworkspace.json'):
assert mu.logic.get_workspace_dir() == should_be
# read from our demo settings.json
with mock.patch('mu.logic.get_settings_path',
return_value='tests/settings.json'):
assert mu.logic.get_workspace_dir() == '/home/foo/mycode'


def test_check_flake():
"""
Ensure the check_flake method calls PyFlakes with the expected code
Expand Down Expand Up @@ -303,8 +318,8 @@ def test_editor_init():
assert e.repl is None
assert e.theme == 'day'
assert mkd.call_count == 2
assert mkd.call_args_list[0][0][0] == mu.logic.PYTHON_DIRECTORY
assert mkd.call_args_list[1][0][0] == mu.logic.DATA_DIR
assert mkd.call_args_list[0][0][0] == mu.logic.DATA_DIR
assert mkd.call_args_list[1][0][0] == mu.logic.get_workspace_dir()


def test_editor_restore_session():
Expand Down Expand Up @@ -543,7 +558,8 @@ def test_add_fs_no_repl():
ed = mu.logic.Editor(view)
with mock.patch('mu.logic.microfs.get_serial', return_value=True):
ed.add_fs()
view.add_filesystem.assert_called_once_with(home=mu.logic.PYTHON_DIRECTORY)
workspace = mu.logic.get_workspace_dir()
view.add_filesystem.assert_called_once_with(home=workspace)
assert ed.fs


Expand Down Expand Up @@ -905,9 +921,10 @@ def test_save_no_path():
ed = mu.logic.Editor(view)
with mock.patch('builtins.open', mock_open):
ed.save()
mock_open.assert_called_once_with('foo.py', 'w', newline='')
assert mock_open.call_count == 2
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to explain this change:
before only one file would be opened that being foo.py for writing therefore assert_called_once_with worked file however getting the folder for that file to be saved into now requires opening settings.json which of course means 2 file operations hence the change in this test.

mock_open.assert_called_with('foo.py', 'w', newline='')
mock_open.return_value.write.assert_called_once_with('foo')
view.get_save_path.assert_called_once_with(mu.logic.PYTHON_DIRECTORY)
view.get_save_path.assert_called_once_with(mu.logic.get_workspace_dir())


def test_save_no_path_no_path_given():
Expand Down Expand Up @@ -1101,7 +1118,7 @@ def test_quit_modified_ok():
ed.quit(mock_event)
assert view.show_confirmation.call_count == 1
assert mock_event.ignore.call_count == 0
assert mock_open.call_count == 1
assert mock_open.call_count == 2
assert mock_open.return_value.write.call_count > 0


Expand All @@ -1128,7 +1145,7 @@ def test_quit_save_tabs_with_paths():
ed.quit(mock_event)
assert view.show_confirmation.call_count == 1
assert mock_event.ignore.call_count == 0
assert mock_open.call_count == 1
assert mock_open.call_count == 2
assert mock_open.return_value.write.call_count > 0
recovered = ''.join([i[0][0] for i
in mock_open.return_value.write.call_args_list])
Expand Down Expand Up @@ -1159,7 +1176,7 @@ def test_quit_save_theme():
ed.quit(mock_event)
assert view.show_confirmation.call_count == 1
assert mock_event.ignore.call_count == 0
assert mock_open.call_count == 1
assert mock_open.call_count == 2
assert mock_open.return_value.write.call_count > 0
recovered = ''.join([i[0][0] for i
in mock_open.return_value.write.call_args_list])
Expand Down