Commit de7c0546 authored by Grant Paton-Simpson's avatar Grant Paton-Simpson

Allow users to choose warnings only; misc

* Enable warnings-only filtering
* Change option names e.g. displayer -> output
* Update README - add new use case, and info on interface changes
* Add warning status and title to output of advice list
* Misc renaming inside code e.g. message_level -> detail_level
* Add misc additional internal code docs
* Bump version
parent b3cea2de
Pipeline #583 canceled with stages
......@@ -5,8 +5,8 @@ git:
sed -i 's/RECORD_AST = t/RECORD_AST = f/' /home/g/projects/superhelp/superhelp/conf.py
sed -i 's/DEV_MODE = t/DEV_MODE = f/' /home/g/projects/superhelp/superhelp/conf.py
sed -i 's/DO_HTML = f/DO_HTML = t/' /home/g/projects/superhelp/superhelp/conf.py
sed -i 's/DISPLAYER = c/DISPLAYER = h/' /home/g/projects/superhelp/superhelp/conf.py
sed -i 's/DISPLAYER = m/DISPLAYER = h/' /home/g/projects/superhelp/superhelp/conf.py
sed -i 's/OUTPUT = c/OUTPUT = h/' /home/g/projects/superhelp/superhelp/conf.py
sed -i 's/OUTPUT = m/OUTPUT = h/' /home/g/projects/superhelp/superhelp/conf.py
git status
upload:
......
......@@ -45,27 +45,37 @@ or similar
## Example Use Cases
* Charlotte is a Python beginner and wants to get advice on a five-line
function she wrote to display greetings to a list of people. She learns about
* Charlotte likes to check her code before others see it so includes
import superhelp
superhelp.this(warnings_only=True)
at the top of each script. When she is happy with the code she comments
those two lines out.
* Avi is a Python beginner and wants to get advice on a five-line
function he wrote to display greetings to a list of people. He learns about
Python conventions for variable naming and better ways of combining strings.
* Avi wants to get advice on a named tuple. He learns how to add doc strings
* Zach wants to get advice on a named tuple. He learns how to add doc strings
to individual fields.
* Zach is considering submitting some code to Stack Overflow but wants to
improve it first (or possibly get ideas for a solution directly). He discovers
that a list comprehension might work. He also becomes aware of dictionary
* Noor is considering submitting some code to Stack Overflow but wants to
improve it first (or possibly get ideas for a solution directly). She discovers
that a list comprehension might work. She also becomes aware of dictionary
comprehensions for the first time.
* Noor has written a simple Python decorator but is wanting to see if there is
anything which can be improved. She learns how to use functool.wrap from an
* Al has written a simple Python decorator but is wanting to see if there is
anything which can be improved. He learns how to use functool.wrap from an
example provided.
* Al is an experienced Python developer but tends to forget things like doc
strings in his functions. He learns a standard approach and starts using it
more often.
* Moana is an experienced Python developer but tends to forget things like doc
strings in her functions. She learns a standard approach and starts using it
more often. Moana also finds the summarised linting useful.
* Moana wants to check the quality of some code before including it in her project. She learns about some issues and makes improvements before integrating it.
* Paul wants to check the quality of some code before including it in his
project. He learns about some issues and makes improvements before integrating
it.
# Example Usage
......@@ -104,32 +114,62 @@ Put the following at the top of your script and then run the script (note - ther
import superhelp
superhelp.this()
If you don't want the default web output you can specify another displayer such as 'cli' (command line interface) or 'md' (markdown):
If you don't want the default web output you can specify another output such as 'cli' (command line interface) or 'md' (markdown):
import superhelp
superhelp.this(displayer='md')
superhelp.this(output='md')
If you don't want the default 'Extra' level of messages you can specify a different message_level ('Brief' or 'Main') e.g.
If you don't want the default 'Extra' level of messages you can specify a different detail level ('Brief' or 'Main') e.g.
import superhelp
superhelp.this(displayer='md', message_level='Brief')
superhelp.this(output='md', detail_level='Brief')
or:
import superhelp
superhelp.this(message_level='Main')
superhelp.this(detail_level='Main')
If you only want to see warnings you can specify warnings only e.g.
import superhelp
superhelp.this(warnings_only=True)
**Changes since 0.9.21**:
Changed:
output (replaces displayer)
detail_level (replaces level)
Added:
warnings_only
### From the command line (terminal / console)
$ shelp -h ## get help on usage
$ shelp --snippet "people = ['Tomas', 'Sal', 'Raj']" --displayer html --level Main
$ shelp -s "people = ['Tomas', 'Sal', 'Raj']" -d html -l Main
$ shelp --code "people = ['Tomas', 'Sal', 'Raj']" --output html --detail-level Main
$ shelp -c "people = ['Tomas', 'Sal', 'Raj']" -o html -d Main
$ shelp --file-path my_script.py --output cli --detail-level Extra
$ shelp -f my_snippet.py -o cli -d Brief
$ shelp -w --code "people = ['Tomas', 'Sal', 'Raj']"
$ shelp --warnings-only --code "people = ['Tomas', 'Sal', 'Raj']"
$ shelp ## to see advice on an example snippet displayed (detail level 'Extra')
**Changes since 0.9.21**:
Changed:
--output and -o (replaces --displayer and -d)
--detail-level and -d (replaces --level and -l)
$ shelp --file-path my_snippet.py --displayer cli --level Extra
$ shelp -f snippet1.txt -d cli -l Brief
Added:
$ shelp ## to see advice on an example snippet displayed (level Extra)
--warnings-only and -w
## Stretch Ideas
......
......@@ -188,10 +188,11 @@ def get_advisor_comments():
all_advisors_dets = (
FILT_BLOCK_ADVISORS + ANY_BLOCK_ADVISORS + ALL_BLOCKS_ADVISORS)
for advisor_dets in all_advisors_dets:
source = advisor_dets.advisor.__module__.split('.')[-1]
docstring = advisor_dets.advisor.__doc__
advisor_comment = get_docstring_start(docstring)
advisor_comments.append((advisor_comment, source))
source = advisor_dets.advisor.__module__.split('.')[-1]
warning = 'Warning: ' if advisor_dets.warning else ''
advisor_comments.append((advisor_comment, source, warning))
return advisor_comments
## =============================================================================
......
......@@ -14,8 +14,8 @@ m = 'md'
RECORD_AST = f ## (f)
DEV_MODE = f ## (f)
DISPLAYER = h ## set html displayer as default (h)
DO_DISPLAYER = t ## f is only ever used when testing pre-display (t)
OUTPUT = h ## set html as default output (h)
SHOW_OUTPUT = t ## f is only ever used when testing pre-display (t)
INCLUDE_LINTING = t ## f when running unit tests to massively speed them up (otherwise every snippet in tests is linted each time) (t)
## =============================================================================
......@@ -54,7 +54,7 @@ MD_PYTHON_CODE_START = '::python'
BRIEF = 'Brief' ## no spaces; used as labels and as parts of class names in CSS
MAIN = 'Main'
EXTRA = 'Extra'
MESSAGE_LEVELS = [BRIEF, MAIN, EXTRA]
DETAIL_LEVELS = [BRIEF, MAIN, EXTRA]
ANON_NAME = 'Anonymous'
......@@ -109,6 +109,11 @@ METHOD_LBL = 'method'
EMAIL2USE = 'superhelp@p-s.co.nz'
WARNINGS_ONLY_MSG = ("Only displaying warnings. "
"To see all help, set warnings only option to False")
ALL_HELP_SHOWING_MSG = ("Displaying all help. "
"To only see warnings, set warnings only option to True")
INTRO = ("Help is provided for your overall snippet and for each block of code "
"as appropriate. If there is nothing to say about a block it is skipped.")
NO_ADVICE_MESSAGE = ("No advice to give - looks fine :-). But if you think "
......
......@@ -18,9 +18,9 @@ TERMINAL_WIDTH = 80
MDV_CODE_BOUNDARY = "```"
def get_message(message_dets, message_level):
message = dedent(message_dets.message[message_level])
if message_level == conf.EXTRA:
def get_message(message_dets, detail_level):
message = dedent(message_dets.message[detail_level])
if detail_level == conf.EXTRA:
message = dedent(message_dets.message[conf.MAIN]) + message
message = dedent(message)
message = (message
......@@ -45,19 +45,25 @@ def _need_snippet_displayed(overall_messages_dets, block_messages_dets, *,
return True
def display(snippet, messages_dets, *,
message_level=conf.BRIEF, in_notebook=False, multi_block=False):
detail_level=conf.BRIEF,
warnings_only=False, in_notebook=False, multi_block=False):
"""
Show by code blocks.
"""
logging.debug(f"{__name__} doesn't use in_notebook setting {in_notebook}")
md2cli.term_columns = TERMINAL_WIDTH
if warnings_only:
options_msg = conf.WARNINGS_ONLY_MSG
else:
options_msg = conf.ALL_HELP_SHOWING_MSG
text = [
md2cli.main(layout(f"""\
# SuperHELP - Help for Humans!
{conf.INTRO}
Currently showing {message_level} content as requested.
Currently showing {detail_level} content as requested.
{options_msg}.
{conf.MISSING_ADVICE_MESSAGE}
"""
......@@ -74,7 +80,7 @@ def display(snippet, messages_dets, *,
+ line_numbered_snippet
+ f"\n{MDV_CODE_BOUNDARY}")))
for message_dets in overall_messages_dets:
message = get_message(message_dets, message_level)
message = get_message(message_dets, detail_level)
text.append(message)
block_messages_dets.sort(key=lambda nt: (nt.first_line_no, nt.warning))
prev_line_no = None
......@@ -99,7 +105,7 @@ def display(snippet, messages_dets, *,
""")))
block_has_warning_header = True
## process message
message = get_message(message_dets, message_level)
message = get_message(message_dets, detail_level)
text.append(message)
content = '\n'.join(text)
print(content)
......@@ -6,9 +6,9 @@ from ..utils import get_line_numbered_snippet, make_open_tmp_file
from markdown import markdown ## https://coderbook.com/@marcus/how-to-render-markdown-syntax-as-html-using-python/ @UnresolvedImport
MESSAGE_LEVEL2CLASS = {
message_level: f"help help-{message_level}"
for message_level in conf.MESSAGE_LEVELS}
DETAIL_LEVEL2CLASS = {
detail_level: f"help help-{detail_level}"
for detail_level in conf.DETAIL_LEVELS}
LOGO_SVG = """\
<svg
......@@ -464,10 +464,10 @@ VISIBILITY_SCRIPT = """\
PART = 'part'
IS_CODE = 'is_code'
def _get_radio_buttons(*, message_level=conf.BRIEF):
def _get_radio_buttons(*, detail_level=conf.BRIEF):
radio_buttons_dets = []
for message_type in conf.MESSAGE_LEVELS:
checked = ' checked' if message_type == message_level else ''
for message_type in conf.DETAIL_LEVELS:
checked = ' checked' if message_type == detail_level else ''
radio_button_dets = f"""\
<input type="radio"
id="radio-verbosity-{message_type}"
......@@ -512,7 +512,7 @@ def get_separate_code_message_parts(message):
def get_html_strs(message, message_type, *, warning=False): # @UnusedVariable
if not message:
return []
message_type_class = MESSAGE_LEVEL2CLASS[message_type]
message_type_class = DETAIL_LEVEL2CLASS[message_type]
str_html_list = [f"<div class='{message_type_class}'>", ]
message_parts = get_separate_code_message_parts(message)
for message_part in message_parts:
......@@ -533,13 +533,13 @@ def get_message_html_strs(message_dets):
message_html_strs = []
if message_dets.warning:
message_html_strs.append("<div class='warning'>")
for message_level in conf.MESSAGE_LEVELS:
for detail_level in conf.DETAIL_LEVELS:
try:
message = message_dets.message[message_level]
message = message_dets.message[detail_level]
except KeyError:
if message_level != conf.EXTRA:
if detail_level != conf.EXTRA:
raise Exception(
f"Missing required message level {message_level}")
f"Missing required message level {detail_level}")
except TypeError:
raise TypeError(
f"Missing message in message_dets {message_dets}")
......@@ -552,9 +552,9 @@ def get_message_html_strs(message_dets):
)
except Exception:
pass
message_level_html_strs = get_html_strs(
message, message_level, warning=message_dets.warning)
message_html_strs.extend(message_level_html_strs)
detail_level_html_strs = get_html_strs(
message, detail_level, warning=message_dets.warning)
message_html_strs.extend(detail_level_html_strs)
if message_dets.warning:
message_html_strs.append("</div>")
return message_html_strs
......@@ -590,7 +590,7 @@ def _need_snippet_displayed(overall_messages_dets, block_messages_dets, *,
return True
def _get_all_html_strs(snippet, overall_messages_dets, block_messages_dets, *,
in_notebook=False, multi_block=False):
warnings_only=False, in_notebook=False, multi_block=False):
"""
Display all message types - eventually will show brief and, if the user
clicks to expand, main instead with the option of expanding to show Extra.
......@@ -600,6 +600,13 @@ def _get_all_html_strs(snippet, overall_messages_dets, block_messages_dets, *,
"""
all_html_strs = []
## any feedback on user options chosen
if not in_notebook:
if warnings_only:
options_msg = f"<p>{conf.WARNINGS_ONLY_MSG}.</p>"
else:
options_msg = f"<p>{conf.ALL_HELP_SHOWING_MSG}.</p>"
all_html_strs.append(options_msg)
## overall snippet display
display_snippet = _need_snippet_displayed(
overall_messages_dets, block_messages_dets,
......@@ -652,7 +659,8 @@ def _get_head(*, in_notebook=False):
return head
def display(snippet, messages_dets, *,
message_level=conf.BRIEF, in_notebook=False, multi_block=False):
detail_level=conf.BRIEF,
in_notebook=False, warnings_only=False, multi_block=False):
"""
Show for overall snippet and then by code blocks as appropriate.
......@@ -660,10 +668,10 @@ def display(snippet, messages_dets, *,
and display
"""
intro = f"<p>{conf.INTRO}</p>"
radio_buttons = _get_radio_buttons(message_level=message_level)
radio_buttons = _get_radio_buttons(detail_level=detail_level)
overall_messages_dets, block_messages_dets = messages_dets
all_html_strs = _get_all_html_strs(snippet,
overall_messages_dets, block_messages_dets,
overall_messages_dets, block_messages_dets, warnings_only=warnings_only,
in_notebook=in_notebook, multi_block=multi_block)
body_inner = '\n'.join(all_html_strs)
head = _get_head(in_notebook=in_notebook)
......
......@@ -17,9 +17,9 @@ from .. import conf
MDV_CODE_START = MDV_CODE_END = "```"
def get_message(message_dets, message_level):
message = dedent(message_dets.message[message_level])
if message_level == conf.EXTRA:
def get_message(message_dets, detail_level):
message = dedent(message_dets.message[detail_level])
if detail_level == conf.EXTRA:
message = dedent(message_dets.message[conf.MAIN]) + message
message = dedent(message)
message = (message
......@@ -43,18 +43,24 @@ def _need_snippet_displayed(overall_messages_dets, block_messages_dets, *,
return True
def display(snippet, messages_dets, *,
message_level=conf.BRIEF, in_notebook=False, multi_block=False):
detail_level=conf.BRIEF,
warnings_only=False, in_notebook=False, multi_block=False):
"""
Show by code blocks.
"""
logging.debug(f"{__name__} doesn't use in_notebook setting {in_notebook}")
if warnings_only:
options_msg = conf.WARNINGS_ONLY_MSG
else:
options_msg = conf.ALL_HELP_SHOWING_MSG
text = [
layout(f"""\
# SuperHELP - Help for Humans!
{conf.INTRO}
Currently showing {message_level} content as requested.
Currently showing {detail_level} content as requested.
{options_msg}.
{conf.MISSING_ADVICE_MESSAGE}
"""
......@@ -71,7 +77,7 @@ def display(snippet, messages_dets, *,
+ line_numbered_snippet
+ f"\n{MDV_CODE_END}"))
for message_dets in overall_messages_dets:
message = get_message(message_dets, message_level)
message = get_message(message_dets, detail_level)
text.append(message)
block_messages_dets.sort(key=lambda nt: (nt.first_line_no, nt.warning))
prev_line_no = None
......@@ -96,7 +102,7 @@ def display(snippet, messages_dets, *,
"""))
block_has_warning_header = True
## process message
message = get_message(message_dets, message_level)
message = get_message(message_dets, detail_level)
text.append(message)
content = '\n'.join(text)
tmp_fh, fpath = make_open_tmp_file('superhelp_output.md', mode='w')
......
......@@ -24,16 +24,18 @@ logging.basicConfig(
advisors.load_advisors()
def display_messages(displayer, snippet, messages_dets, *,
message_level=conf.BRIEF, in_notebook=False, multi_block=False):
detail_level=conf.BRIEF,
warnings_only=False, in_notebook=False, multi_block=False):
res = displayer.display(snippet, messages_dets,
message_level=message_level, in_notebook=in_notebook,
detail_level=detail_level,
warnings_only=warnings_only, in_notebook=in_notebook,
multi_block=multi_block)
if in_notebook:
return res
def _get_snippet(snippet, file_path):
if snippet and file_path:
raise Exception("Either set snippet or file-path, not both")
raise Exception("Either set code or file-path, not both")
elif file_path:
with open(file_path) as f:
snippet = f.read()
......@@ -52,30 +54,32 @@ def _get_snippet(snippet, file_path):
)
return snippet
def _get_displayer_module(displayer):
def _get_displayer_module(output):
ARG2DISPLAYER = {
'html': html_displayer,
'cli': cli_displayer,
'md': md_displayer,
}
displayer_module = ARG2DISPLAYER.get(displayer)
displayer_module = ARG2DISPLAYER.get(output)
if displayer_module is None:
logging.info("Display is currently suppressed - please supply "
"a displayer if you want advice displayed")
return displayer_module
def get_advice(snippet=None, *, file_path=None, displayer='html',
message_level=conf.EXTRA, in_notebook=False):
def get_help(snippet=None, *, file_path=None,
output='html', detail_level=conf.EXTRA,
warnings_only=False, in_notebook=False):
"""
Provide advice about the snippet supplied
Provide advice about the snippet of Python code supplied
:param str snippet: (optional) snippet of valid Python code to provide
advice on. If None will try the file_path and if that is None will use the
default snippet
:param str file_path: (optional) file path containing snippet
:param str displayer: displayer to use e.g. 'html' or 'cli'. Defaults to
:param str snippet: (optional) snippet of valid Python code to get help for.
If None will try the file_path and if that is None will use the default
snippet.
:param str file_path: (optional) file path containing Python code
:param str output: type of output e.g. 'html', 'cli', 'md'. Defaults to
'html'.
:param str message_level: e.g. 'Brief', 'Main', 'Extra'
:param str detail_level: e.g. 'Brief', 'Main', 'Extra'
:param bool warnings_only: if True only displays warnings
:param bool in_notebook: if True might change way display happens e.g. HTML
not sent to browser but returned for display by notebook itself
"""
......@@ -88,14 +92,16 @@ def get_advice(snippet=None, *, file_path=None, displayer='html',
multi_block = False
else:
try:
messages_dets, multi_block = messages.get_snippet_dets(snippet)
messages_dets, multi_block = messages.get_snippet_dets(
snippet, warnings_only=warnings_only)
except Exception as e:
messages_dets = messages.get_error_messages_dets(e, snippet)
multi_block = False
displayer_module = _get_displayer_module(displayer)
displayer_module = _get_displayer_module(output)
if displayer_module:
res = display_messages(displayer_module, snippet, messages_dets,
message_level=message_level, in_notebook=in_notebook,
res = display_messages(displayer_module, snippet,
messages_dets, detail_level=detail_level,
warnings_only=warnings_only, in_notebook=in_notebook,
multi_block=multi_block)
if in_notebook:
return res
......@@ -124,18 +130,29 @@ def _get_introspected_file_path():
file_path = calling_item.filename
return file_path
def this(*, displayer='html', message_level=conf.EXTRA, file_path=None):
def this(*, output='html', detail_level=conf.EXTRA,
warnings_only=False, file_path=None):
"""
Get SuperHELP output on the file_path Python script.
Yes - if you "import this" later I've shaded it in this namespace by calling
this function, err, this. But I thought it more important to expose the
simple superhelp.this() interface than namespace purity :-).
Yes - this messes up the ability to "import this" later because I've shaded
the name "this" in this namespace by naming this function, err ..., "this".
But it was considered of paramount importance to give users a simple and
easy-to-remember superhelp.this() interface.
:param str output: type of output e.g. 'html', 'cli', 'md'. Defaults to
'html'.
:param str detail_level: e.g. 'Brief', 'Main', 'Extra'
:param bool warnings_only: if True only displays warnings
:param str / Path file_path: full path to script location. Only needed if
SuperHELP is unable to locate the script by itself. Usually should be
__file__ (note the double underscores on either side of file).
"""
if not file_path:
file_path = _get_introspected_file_path()
get_advice(snippet=None, file_path=file_path, displayer=displayer,
message_level=message_level, in_notebook=False)
get_help(snippet=None, file_path=file_path,
output=output, detail_level=detail_level,
warnings_only=warnings_only, in_notebook=False)
def shelp():
"""
......@@ -143,36 +160,51 @@ def shelp():
$ shelp -h
"""
default_displayer = conf.DISPLAYER
default_output = conf.OUTPUT
## don't use type=list ever https://stackoverflow.com/questions/15753701/argparse-option-for-passing-a-list-as-option
parser = argparse.ArgumentParser(
description='Superhelp - Help for Humans!')
parser.add_argument('-s', '--snippet', type=str,
parser.add_argument('-c', '--code', type=str,
required=False,
help="Supply a line or brief snippet of Python code")
help=("Python code - usually only a line or snippet. "
"Either supply the code here or via --file-path (-f)"))
parser.add_argument('-f', '--file-path', type=str,
required=False,
help="File location of a line or brief snippet of Python code")
parser.add_argument('-l', '--level', type=str,
help=("File location of a line, snippet, or script of Python code. "
"Either point to the code here or supply it using --code (-c)"))
parser.add_argument('-d', '--detail-level', type=str,
required=False, default='Extra',
help="What level of help do you want? Brief, Main, or Extra?")
parser.add_argument('-d', '--displayer', type=str,
required=False, default=default_displayer,
help="Where do you want your help shown? html, cli, etc")
help="What level of detail do you want? Brief, Main, or Extra?")
parser.add_argument('-o', '--output', type=str,
required=False, default=default_output,
help="How do you want your help shown? html, cli, md, etc")
parser.add_argument('-w', '--warnings-only', action='store_true',
default=False,
help="Show warnings only")
parser.add_argument('-a', '--advice-list', action='store_true',
default=False,
help="List available advice")
args = parser.parse_args()
if args.advice_list:
print("\n======================================")
print("Specific help available from SuperHELP")
print("======================================\n")
advisor_comments = advisors.get_advisor_comments()
num_width = len(str(len(advisor_comments)))
for n, (comment, source) in enumerate(
for n, (comment, source, warning) in enumerate(
advisor_comments, 1):
print(f"{n:>{num_width}}) {comment} ({source})")
print(f"{n:>{num_width}}) {warning}{comment} ({source})")
return
if args.output and args.file_path:
print(
"Either supply code using -c / --code "
"(usually for smaller snippets of Python) "
"OR refer to a file of Python code using -f / --file-path")
return
displayer = args.displayer if conf.DO_DISPLAYER else None
get_advice(args.snippet,
file_path=args.file_path, displayer=displayer, message_level=args.level)
output = args.output if conf.SHOW_OUTPUT else None
get_help(args.code, file_path=args.file_path,
output=output, detail_level=args.detail_level,
warnings_only=args.warnings_only, in_notebook=False)
if __name__ == '__main__':
shelp()
......@@ -10,6 +10,7 @@ from astpath.asts import _set_encoded_literal, _strip_docstring
## Monkey-patch as at astpath Python 3.8 as at 2020-04-26
## Need to be able to tell val = 1 from val = '1' (that little detail ;-))
## Pull request fixing this was accepted and merged May 2020
def convert_to_xml(node, omit_docstrings=False, node_mappings=None):
"""Convert supplied AST node to XML."""
possible_docstring = isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.Module))
......@@ -239,7 +240,7 @@ def _get_filtered_blocks_dets(advisor_dets, xml, blocks_dets):
logging.debug(f"{advisor_dets.advisor_name} had no blocks")
return filtered_blocks_dets
def get_block_level_messages_dets(blocks_dets, xml):
def get_block_level_messages_dets(blocks_dets, xml, *, warnings_only=False):
"""
For each advisor, get advice on every relevant block. Element type specific
advisors process filtered blocks_dets; all block advisors process all blocks
......@@ -253,6 +254,8 @@ def get_block_level_messages_dets(blocks_dets, xml):
advisors.FILT_BLOCK_ADVISORS + advisors.ANY_BLOCK_ADVISORS)
for advisor_dets in all_advisors_dets:
logging.debug(f"About to process '{advisor_dets.advisor_name}'")
if warnings_only and not advisor_dets.warning:
continue
element_filtering = hasattr(advisor_dets, 'xpath')
if element_filtering:
filtered_blocks_dets = _get_filtered_blocks_dets(
......@@ -274,7 +277,8 @@ def get_block_level_messages_dets(blocks_dets, xml):
repeat = True
return messages_dets
def get_overall_snippet_messages_dets(snippet, blocks_dets):
def get_overall_snippet_messages_dets(snippet, blocks_dets, *,
warnings_only=False):
"""
Returns messages which apply to snippet as a whole, not just specific
blocks. E.g. looking at every block to look for opportunities to unpack. Or
......@@ -284,6 +288,9 @@ def get_overall_snippet_messages_dets(snippet, blocks_dets):
all_advisors_dets = (
advisors.ALL_BLOCKS_ADVISORS + advisors.SNIPPET_STR_ADVISORS)
for advisor_dets in all_advisors_dets:
logging.debug(f"About to process '{advisor_dets.advisor_name}'")
if warnings_only and not advisor_dets.warning:
continue
if advisor_dets.input_type == conf.BLOCKS_DETS:
advisor_input = blocks_dets
elif advisor_dets.input_type == conf.SNIPPET_STR:
......@@ -298,7 +305,8 @@ def get_overall_snippet_messages_dets(snippet, blocks_dets):
messages_dets.append(message_dets)
return messages_dets
def get_separated_messages_dets(snippet, snippet_block_els, xml):
def get_separated_messages_dets(snippet, snippet_block_els, xml, *,
warnings_only=False):
"""
Break snippet up into syntactical parts and blocks of code. Apply advisor
functions and get message details. Split into overall messages and block-
......@@ -307,6 +315,7 @@ def get_separated_messages_dets(snippet, snippet_block_els, xml):
:param str snippet: code snippet
:param list snippet_block_els: list of block elements for snippet
:param xml xml: snippet code as xml object
:param bool warnings_only: if True, warnings only
:return: a tuple of two MessageDets lists
(overall_snippet_messages_dets, block_level_messages_dets)
or None if no messages
......@@ -314,8 +323,9 @@ def get_separated_messages_dets(snippet, snippet_block_els, xml):
"""
blocks_dets = get_blocks_dets(snippet, snippet_block_els)
overall_snippet_messages_dets = get_overall_snippet_messages_dets(
snippet, blocks_dets)
block_level_messages_dets = get_block_level_messages_dets(blocks_dets, xml)
snippet, blocks_dets, warnings_only=warnings_only)
block_level_messages_dets = get_block_level_messages_dets(
blocks_dets, xml, warnings_only=warnings_only)
for messages_dets in [
overall_snippet_messages_dets, block_level_messages_dets]:
if None in messages_dets:
......@@ -337,7 +347,15 @@ def store_ast_output(xml):
xml.getroottree().write(str(tmp_ast_output_xml_fpath), pretty_print=True)
logging.info("\n\n\n\n\nUpdating AST\n\n\n\n\n")
def get_snippet_dets(snippet):
def get_snippet_dets(snippet, warnings_only=False):
"""
Get details for snippet of code.
:return: snippet_messages_dets
(overall_snippet_messages_dets, block_level_messages_dets),
multi_block_snippet (bool)
:rtype: tuple
"""
tree = _get_tree(snippet)
xml = astpath.asts.convert_to_xml(tree)
if conf.RECORD_AST:
......@@ -345,7 +363,7 @@ def get_snippet_dets(snippet):
snippet_block_els = xml.xpath('body')[0].getchildren() ## [0] because there is only one body under root
multi_block_snippet = len(snippet_block_els) > 1
snippet_messages_dets = get_separated_messages_dets(
snippet, snippet_block_els, xml)
snippet, snippet_block_els, xml, warnings_only=warnings_only)