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

Style customised linter messages:

* Add headings
* Allow custom content to include highlighted code and headings
* Display custom content first
parent 4a49e42d
......@@ -180,8 +180,9 @@ def if_else_overview(block_dets, *, repeat=False):
When using `if`, `elif`, or `else`, the item evaluated can
either be an expression or a name (variable). Sometimes your
code can be more readable if the expression is precalculated and
a name is supplied instead. For example:
code can be more readable if you make an intermediate variable
and use that instead. Shorter, more readable, easier to check
the parts. For example:
""")
+
......
This diff is collapsed.
......@@ -27,14 +27,58 @@ else:
## All snippets here should be raw strings (see https://stackoverflow.com/questions/53636723/python-parsing-code-with-new-line-character-in-them-using-ast)
TEST_SNIPPET = r"""
def sorted(*G, **kwargs):
for i in range(len(G)):
for j in range(1,len(G)):
if G[j-1]<G[j]:
G[j-1],G[j]=G[j],G[j-1]
G = [['Ahmad', 3.8], ['Rizwan', 3.68], ['Bilal', 3.9]]
sorted(G)
print(G)
def function_with_really_long_name(parameter_1, parameter_2,
parameter_3):
pass
def function_with_really_long_name2(
parameter_1, parameter_2, parameter_3):
pass
def function_with_really_long_name3(
parameter_1,
parameter_2,
parameter_3):
pass
def function_with_really_long_name4(
parameter_1,
parameter_2,
parameter_3):
pass
def function_with_really_long_name5(
parameter_1,
parameter_2,
parameter_3):
pass
my_list = [
'a',
]
my_list2 = [
'a',
]
my_list3 = [
'a',
]
my_list4 = [
'a',
]
a = 'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvaaaavvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv'
b = 'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvbbvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv'
c = 'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvcvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv'
func1("abc"
"def")
func2("abc"
"def")
func3("abc"
"def")
"""
PY3_6 = '3.6'
......@@ -105,6 +149,8 @@ METHOD_LBL = 'method'
EMAIL2USE = 'superhelp@p-s.co.nz'
INTRO = ("Help is provided for your overall snippet and for each block of code "
"as appropriate.")
NO_ADVICE_MESSAGE = ("No advice to give - looks fine :-). But if you think "
f"there should have been some advice given, contact {EMAIL2USE} "
"with the subject line 'Advice' and explain. Please include a snippet to "
......
......@@ -52,8 +52,7 @@ def display(snippet, messages_dets, *,
md2cli.main(layout(f"""\
# SuperHELP - Help for Humans!
Help is provided for your overall snippet and for each block of code
as appropriate.
{conf.INTRO}
Currently showing {message_level} content as requested.
......
......@@ -259,7 +259,7 @@ BROWSER_HTML_WRAPPER = """\
<body>
{logo_svg}
<h1>SuperHELP - Help for Humans!</h1>
<p>Help is provided for each block of code in your snippet.</p>
{intro}
<p>{missing_advice_message}</p>
<p>Toggle between different levels of detail.</p>
{radio_buttons}
......@@ -274,7 +274,7 @@ NOTEBOOK_HTML_WRAPPER = """\
{head}
<body>
<h1>Look here for some help on the snippet in the cell above</h1>
<p>Help is provided for each block of code in your snippet.</p>
{intro}
<p>{missing_advice_message}</p>
<p>Toggle between different levels of detail.</p>
{radio_buttons}
......@@ -659,6 +659,7 @@ def display(snippet, messages_dets, *,
:param bool in_notebook: if True, return HTML as string; else open browser
and display
"""
intro = f"<p>{conf.INTRO}</p>"
radio_buttons = _get_radio_buttons(message_level=message_level)
overall_messages_dets, block_messages_dets = messages_dets
all_html_strs = _get_all_html_strs(snippet,
......@@ -670,6 +671,7 @@ def display(snippet, messages_dets, *,
html2write = NOTEBOOK_HTML_WRAPPER.format(
head=head,
radio_buttons=radio_buttons,
intro=intro,
missing_advice_message=conf.MISSING_ADVICE_MESSAGE,
body_inner=body_inner,
visibility_script=VISIBILITY_SCRIPT)
......@@ -678,6 +680,7 @@ def display(snippet, messages_dets, *,
html2write = BROWSER_HTML_WRAPPER.format(
head=head, logo_svg=LOGO_SVG,
radio_buttons=radio_buttons,
intro=intro,
missing_advice_message=conf.MISSING_ADVICE_MESSAGE,
body_inner=body_inner,
visibility_script=VISIBILITY_SCRIPT)
......
from collections import namedtuple
from . import conf # @UnresolvedImport
from .utils import layout_comment as layout
## extract patterns e.g. from:
## /tmp/snippet.py:1:23: W291 trailing whitespace
## /tmp/snippet.py:4:1: E999 IndentationError: unexpected indent
LINT_PATTERN = fr"""^.+?: ## starts with misc until first colon e.g. /tmp/snippet.py: (+? to be non-greedy)
(?P<{conf.LINT_LINE_NO}>\d+) ## line_no = one or more digits after snippet.py: and before the next : e.g. 4
(?P<{conf.LINT_LINE_NO}>\d+) ## line_no = one or more digits after snippet.py: and before the next : e.g. 4 # @UndefinedVariable
:\d+:\s{{1}} ## one or more digits, :, and one space e.g. '1: '
(?P<{conf.LINT_MSG_TYPE}>\w{{1}}\d{{1,3}})\s{{1}} ## msg_type = (one letter, 1-3 digits) and one space e.g. E999
(?P<{conf.LINT_MSG_TYPE}>\w{{1}}\d{{1,3}})\s{{1}} ## msg_type = (one letter, 1-3 digits) and one space e.g. 'E999 '
(?P<{conf.LINT_MSG}>.*) ## msg = everything else e.g. 'IndentationError: unexpected indent'
"""
## https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes
## https://flake8.pycqa.org/en/latest/user/error-codes.html
IGNORED_LINT_RULES = [
'E128', ## flake8 wants visual alignment on line continuation - I don't - I want standardised alignment on indent multiples of 4 (idea taken from Brandon Rhodes thanks Brandon!)
'E266', 'E262', ## I like ## before comments and # before commented out code (idea copied off Tom Eastman - thanks Tom!)
'E305', ## for classes I agree with 2 spaces but not functions
]
def title2msg_type(title):
return title.upper().replace(' ', '_') + '_MSG_TYPE'
## Ensure brief AND main are the same so titles don't shift when
## changing message level
line_continuation_title = "Line continuation"
line_length_title = "Line length"
unused_imports = "Unused imports"
## nice to keep placeholder names etc aligned with actual titles but nothing breaks if we don't
LINE_CONTINUATION_MSG_TYPE = title2msg_type(line_continuation_title)
LINE_LENGTH_MSG_TYPE = title2msg_type(line_length_title)
UNUSED_IMPORT_MSG_TYPE = title2msg_type(unused_imports)
CONSOLIDATE_MSG_TYPE = {
'E123': LINE_CONTINUATION_MSG_TYPE,
'E124': LINE_CONTINUATION_MSG_TYPE,
'E125': LINE_CONTINUATION_MSG_TYPE,
'E126': LINE_CONTINUATION_MSG_TYPE,
'E127': LINE_CONTINUATION_MSG_TYPE,
'E128': LINE_CONTINUATION_MSG_TYPE,
'E129': LINE_CONTINUATION_MSG_TYPE,
'E131': LINE_CONTINUATION_MSG_TYPE,
'E501': LINE_LENGTH_MSG_TYPE,
'F401': UNUSED_IMPORT_MSG_TYPE,
}
LintMsgs = namedtuple('LintMsgs', 'brief, main, extra, replacement')
CUSTOM_LINT_MSGS = {
'E501': LintMsgs(
"""\
One or more lines are longer than the recommended 79 characters. This is
not necessarily a problem but long lines should be an exception to the
rule
""",
"""\
One or more lines are longer than the recommended 79 characters. This is
not necessarily a problem given that we have wider monitors than when
the guidelines were formulated. But long lines should be an exception to
the rule. All being equal, short lines are easier to read and understand
than long lines. There are multiple strategies for shortening lines but
the overall goal has to be readability. Sometimes we have to live with
broken "rules". And that's official. Read PEP 8 - the official Python
style guide - especially the section "A Foolish Consistency is the
Hobgoblin of Little Minds".
""",
LINE_CONTINUATION_MSG_TYPE: LintMsgs(
layout(f"""
#### {line_continuation_title}
The linter has raised questions about line continuation. There are
at least two styles of line continuation. Whichever you follow be
consistent.
"""),
(
layout(f"""\
#### {line_continuation_title}
The linter has raised questions about line continuation. There
are at least two styles of line continuation:
1) Visual line continuation e.g. note how parameter_3 lines up
with the opening parenthesis
""")
+
layout("""\
def function_with_long_name(parameter_1, parameter_2,
parameter_3, parameter_4):
'''
Doc string for function
'''
""", is_code=True)
+
layout("""\
Pros:
* a common convention
Cons:
* every time you rename a function you have to realign
parameters
* code will generally not align tidily with multiples of
standard indentation
* room for parameters squeezed
* you have to look to the right to make sense of the function
2) Leftwards alignment with multiples of standard indentation
e.g.
""")
+
layout("""\
def function_with_long_name(parameter_1, parameter_2,
parameter_3, parameter_4):
'''
Doc string for function
'''
""", is_code=True)
+
layout("""\
or
""")
+
layout("""\
def function_with_long_name(
parameter_1, parameter_2, parameter_3, parameter_4):
'''
Doc string for function
'''
""", is_code=True)
+
layout("""\
or
""")
+
layout("""\
def function_with_long_name(
parameter_1,
parameter_2,
parameter_3,
parameter_4):
'''
Doc string for function
'''
""", is_code=True)
+
layout("""\
Pros:
* readability is enhanced because the main content of the code
is as leftward as possible
* beautiful alignment across entire module
Cons:
* you may need to change your IDE's default indenting behaviour
Whichever of the two style you follow, be consistent.
""")
),
layout("""\
See <https://rhodesmill.org/brandon/slides/2012-11-pyconca/#id183>
for some thought-provoking ideas on code style and much more.
"""),
True),
LINE_LENGTH_MSG_TYPE: LintMsgs(
layout(f"""\
#### {line_length_title}
One or more lines are longer than the recommended 79 characters.
This is not necessarily a problem but long lines should be an
exception to the rule.
"""),
layout(f"""\
#### {line_length_title}
One or more lines are longer than the recommended 79 characters.
This is not necessarily a problem given that we have wider monitors
than when the guidelines were formulated. But long lines should be
an exception to the rule. All being equal, short lines are easier to
read and understand than long lines. There are multiple strategies
for shortening lines but the overall goal has to be readability.
Sometimes we have to live with broken "rules". And that's official.
Read PEP 8 - the official Python style guide - especially the
section "A Foolish Consistency is the Hobgoblin of Little Minds".
"""),
'',
True),
'F401': LintMsgs(
"""\
UNUSED_IMPORT_MSG_TYPE: LintMsgs(
layout(f"""\
#### {unused_imports}
One or more imports not used in snippet.
""",
"""\
One or more imports not used in snippet. If the snippet was extracted
from a larger piece of code and the imports are used in that code then
there is no problem.
""",
"""),
layout(f"""\
#### {unused_imports}
One or more imports not used in snippet. If the snippet was
extracted from a larger piece of code and the imports are used in
that code then there is no problem.
"""),
'',
False
)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment