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

Fix of issue 246 - Make help and helpdesk more robust #688

Merged
merged 24 commits into from
Jun 9, 2017
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bdc87b7
nest.helpdesk() opens now system default browser
steffengraber Mar 17, 2017
3e33dea
setting and using help pager/viewer
steffengraber Mar 17, 2017
83346c1
better use in notebook
steffengraber Mar 20, 2017
e7c9f9c
some inline documentation
steffengraber Mar 20, 2017
8a2d69f
introducing check_nb() and moving to hl_api_helper.py
steffengraber Mar 21, 2017
64f18a5
introducing modal window in notebook
steffengraber Mar 21, 2017
7a3b8f8
better help on pure python level
steffengraber Mar 22, 2017
3edd66e
minor helptext edits
steffengraber Mar 22, 2017
cdbc3ab
Merge branch 'master' into fix-issue-246
steffengraber Mar 22, 2017
6fe70e5
Better check for notebooks
steffengraber Mar 23, 2017
3e710ae
private functions
steffengraber Mar 23, 2017
fc66fe2
Secure environment setting
steffengraber Apr 3, 2017
508c008
change from review
steffengraber Apr 21, 2017
5d47a80
Right URL for nest.helpdesk()
steffengraber May 4, 2017
4e124a3
Drop todo
steffengraber May 31, 2017
13caa64
Insert inline documentation
steffengraber May 31, 2017
3b5e86d
Merge branch 'master' into fix-issue-246
steffengraber May 31, 2017
89a5abe
Set default browser to 'more'
steffengraber May 31, 2017
148fdb5
Better .nesrc handling
steffengraber May 31, 2017
4642c8d
Better inline documentation
steffengraber Jun 1, 2017
ee5b97d
Improvement of calling the help
steffengraber Jun 1, 2017
42da7c3
PEP 8
steffengraber Jun 1, 2017
b82fa50
Documentation
steffengraber Jun 7, 2017
a86a32c
Removed string concatenation
steffengraber Jun 9, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 123 additions & 13 deletions pynest/nest/lib/hl_api_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@

import warnings
import inspect
import json
import functools
import textwrap
import subprocess
import os
import re

from string import Template

# These variables MUST be set by __init__.py right after importing.
# There is no safety net, whatsoever.
Expand Down Expand Up @@ -111,7 +117,6 @@ def deprecated(alt_func_name, text=None):
function:
Decorator function
"""

def deprecated_decorator(func):
_deprecation_warning[func.__name__] = True

Expand Down Expand Up @@ -172,6 +177,7 @@ def is_string(obj):
"""
return isinstance(obj, uni_str)


__debug = False


Expand Down Expand Up @@ -229,11 +235,11 @@ def stack_checker_func(*args, **kwargs):
if not get_debug():
return f(*args, **kwargs)
else:
sr('count')
stackload_before = spp()
sr
stackload_before = spp
result = f(*args, **kwargs)
sr('count')
num_leftover_elements = spp() - stackload_before
sr
num_leftover_elements = spp - stackload_before
if num_leftover_elements != 0:
eargs = (f.__name__, num_leftover_elements)
etext = "Function '%s' left %i elements on the stack."
Expand Down Expand Up @@ -389,17 +395,121 @@ def broadcast(item, length, allowed_types, name="item"):
"""

if isinstance(item, allowed_types):
return length * (item, )
return length * (item,)
elif len(item) == 1:
return length * item
elif len(item) != length:
raise TypeError("'%s' must be a single value, a list with " +
"one element or a list with %i elements."
% (name, length))

raise TypeError("'{0}' must be a single value, a list with " +
"one element or a list with {1} elements.".format(
name, length))
return item


def __check_nb():
"""Return true if called from a Jupyter notebook."""
try:
return get_ipython().__class__.__name__.startswith('ZMQ')
except NameError:
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

You could simplify this to (but see comment above)

return iptnk == 'ipython-notebook' or re.search(r'ipython|jupyter', os.environ['_'])



def __show_help_in_modal_window(objname, hlptxt):
"""Open modal window with help text

Parameters
----------
objname : str
filename
hlptxt : str
Full text
"""

hlptxt = json.dumps(hlptxt)
style = "<style>.modal-body p { display: block;unicode-bidi: embed; " \
"font-family: monospace; white-space: pre; }</style>"
s = Template("""
require(
["base/js/dialog"],
function(dialog) {
dialog.modal({
title: '$jstitle',
body: $jstext,
buttons: {
'close': {}
}
});
}
);
""")

from IPython.display import HTML, Javascript, display
display(HTML(style))

display(Javascript(s.substitute(jstitle=objname, jstext=hlptxt)))


def show_help_with_pager(hlpobj, pager):
"""Output of doc in python with pager or print

Parameters
----------
hlpobj : object
Object to display
pager: str, optional
pager to use, NO if you explicity do not want to use a pager
"""
if 'NEST_INSTALL_DIR' not in os.environ:
print(
'NEST help needs to know where NEST is installed.'
'Please source nest_vars.sh or define NEST_INSTALL_DIR manually.')
return

helpdir = os.path.join(os.environ['NEST_INSTALL_DIR'], "share", "doc",
"nest", "help")
objname = hlpobj + '.hlp'
consolepager = ['less', 'more', 'vi', 'vim', 'nano', 'emacs -nw',
'ed', 'editor']

# reading ~/.nestrc lookink for pager to use.
if pager is None:
# open ~/.nestrc
rc = open(os.environ['HOME'] + '/.nestrc', 'r')
for line in rc:
rctst = re.match(r'^\s?%', line)
if rctst is None:
pypagers = re.findall(
r'\s?/page\s?<<\s?/command\s?\((.*)\).*', line)
if pypagers:
pager = pypagers[0]
break
else:
pager = 'less'
rc.close()

for dirpath, dirnames, files in os.walk(helpdir):
for hlp in files:
if hlp == objname:
objf = os.path.join(dirpath, objname)
fhlp = open(objf, 'r')
hlptxt = fhlp.read()
# only for notebook
if __check_nb():
if pager in consolepager:
# only in notebook open modal window
__show_help_in_modal_window(objname, hlptxt)
break
else:
subprocess.call([pager, objf])
break
else:
if pager in consolepager:
subprocess.call([pager, objf])
break
else:
subprocess.call([pager, objf])
break


@check_stack
def get_verbosity():
"""Return verbosity level of NEST's messages.
Expand All @@ -412,8 +522,8 @@ def get_verbosity():

# Defined in hl_api_helper to avoid circular inclusion problem with
# hl_api_info.py
sr('verbosity')
return spp()
sr
return spp


@check_stack
Expand All @@ -429,7 +539,7 @@ def set_verbosity(level):

# Defined in hl_api_helper to avoid circular inclusion problem with
# hl_api_info.py
sr("%s setverbosity" % level)
sr


def model_deprecation_warning(model):
Expand Down
47 changes: 32 additions & 15 deletions pynest/nest/lib/hl_api_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"""

from .hl_api_helper import *
import sys
import os
import webbrowser


@check_stack
Expand Down Expand Up @@ -55,23 +58,36 @@ def authors():


@check_stack
def helpdesk(browser="firefox"):
"""Open the NEST helpdesk in the given browser.
def helpdesk():
"""Open the NEST helpdesk in browser.

The default browser is firefox.

Parameters
----------
browser : str, optional
Name of the browser to use
Use the system default browser.
"""
if 'NEST_DOC_DIR' not in os.environ:
print(
'NEST help needs to know where NEST is installed.'
'Please source nest_vars.sh or define NEST_DOC_DIR manually.')
return

sr("/helpdesk << /command (%s) >> SetOptions" % browser)
sr("helpdesk")
url = os.path.join(os.environ['NEST_DOC_DIR'] + "/help", "helpindex.html")
Copy link
Contributor

Choose a reason for hiding this comment

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

Provide a useful error message if NEST_DOC_DIR is undefined, similar as for missing NEST_INSTALL_DIR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Here you mix os.path.join() with string addition to build a URL. I think since we want a URL, we should not use os.path.join() here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I still have some issues with this line:

  1. This is not a URL, but the name of a local file to be opened by a browser, so the variable name should be helpfile or similar, not url.
  2. Instead of mixing string concatenation and os.path.join(), just use os.path.join():
helpfile = os.path.join(os.environ['NEST_DOC_DIR'], 'help', 'helpindex.html')

"""
Under Windows systems webbrowser.open is incomplete
<https://bugs.python.org/issue8232>
"""
if sys.platform[:3] == "win":
os.startfile(url)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't you use webbrowser also under Windows?

"""
Under MacOs we need to ask for the browser explicitly.
See <https://bugs.python.org/issue30392>.
"""
if sys.platform[:3] == "dar":
webbrowser.get('safari').open_new(url)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not webbrowser.open_new(url) also here? Ok, due to this problem in OSX we need to ask for the browser explicitly under macOS are present (10.12.5). Worse, a failing webbrowser.open_new(url) does not even raise a Python exception but returns happily. So I think we need to keep this, but i would be good to document why we do this.

else:
webbrowser.open_new(url)


@check_stack
def help(obj=None, pager="less"):
def help(obj=None, pager=None):
"""Show the help page for the given object using the given pager.

The default pager is less.
Expand All @@ -83,17 +99,17 @@ def help(obj=None, pager="less"):
pager : str, optional
Pager to use
"""
hlpobj = obj
if hlpobj is not None:
show_help_with_pager(hlpobj, pager)

if obj is not None:
sr("/page << /command (%s) >> SetOptions" % pager)
sr("/%s help" % obj)
else:
print("Type 'nest.helpdesk()' to access the online documentation "
"in a browser.")
print("Type 'nest.help(object)' to get help on a NEST object or "
"command.\n")
print("Type 'nest.Models()' to see a list of available models "
"in NEST.\n")
"in NEST.")
print("Type 'nest.authors()' for information about the makers "
"of NEST.")
print("Type 'nest.sysinfo()' to see details on the system "
Expand All @@ -115,6 +131,7 @@ def get_argv():
tuple:
Argv, as seen by NEST.
"""

sr('statusdict')
statusdict = spp()
return statusdict['argv']
Expand Down