Skip to content

Commit

Permalink
Enhance FileNotFound suggestions and add to README #15
Browse files Browse the repository at this point in the history
  • Loading branch information
SylvainDe committed Jul 1, 2015
1 parent 8e039ec commit dc4a1d2
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 21 deletions.
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ DidYouMean-Python
[![Code Climate](https://codeclimate.com/github/SylvainDe/DidYouMean-Python/badges/gpa.svg)](https://codeclimate.com/github/SylvainDe/DidYouMean-Python)
[![Code Issues](http://www.quantifiedcode.com/api/v1/project/34c9b3f27db24a1ba944fcf3d69a9d2a/badge.svg)](http://www.quantifiedcode.com/app/project/34c9b3f27db24a1ba944fcf3d69a9d2a)

Logic to have various kind of suggestions in case of errors (NameError, AttributeError, ImportError, TypeError, ValueError, SyntaxError, MemoryError, OverflowError).
Logic to have various kind of suggestions in case of errors (NameError, AttributeError, ImportError, TypeError, ValueError, SyntaxError, MemoryError, OverflowError, IOError, OSError).

Inspired by "Did you mean" for Ruby ([Explanation](http://www.yukinishijima.net/2014/10/21/did-you-mean-experience-in-ruby.html), [Github Page](https://github.com/yuki24/did_you_mean)), this is a simple implementation for/in Python. I wanted to see if I could mess around and create something similar in Python and it seems to be possible.

Expand Down Expand Up @@ -97,16 +97,16 @@ pi
##### Looking for missing imports

```python
os.getenv
#>>> Before: NameError("name 'os' is not defined",)
#>>> After: NameError("name 'os' is not defined. Did you mean to import os first?",)
string.ascii_lowercase
#>>> Before: NameError("name 'string' is not defined",)
#>>> After: NameError("name 'string' is not defined. Did you mean to import string first?",)
```
##### Looking in missing imports

```python
getenv
#>>> Before: NameError("name 'getenv' is not defined",)
#>>> After: NameError("name 'getenv' is not defined. Did you mean 'getenv' from os (not imported)?",)
choice
#>>> Before: NameError("name 'choice' is not defined",)
#>>> After: NameError("name 'choice' is not defined. Did you mean 'choice' from random (not imported)?",)
```
##### Special cases

Expand Down Expand Up @@ -239,6 +239,16 @@ range(999999999999999)
#>>> Before: OverflowError('range() result has too many items',)
#>>> After: OverflowError("range() result has too many items. Did you mean 'xrange'?",)
```
### OSError/IOError

##### Suggestion for tilde/variable expansions

```python
os.listdir('~')
#>>> Before: OSError(2, 'No such file or directory')
#>>> After: OSError(2, "No such file or directory. Did you mean '/home/user' (calling os.path.expanduser)?")
```


Usage
-----
Expand Down
2 changes: 1 addition & 1 deletion didyoumean/didyoumean_api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def test_api_ioerror(self):
"""Check the case with IO error suggestion."""
type_ = NoFileIoError
home = os.path.expanduser("~")
sugg = ". Did you mean '" + home + "'?"
sugg = ". Did you mean '" + home + "' (calling os.path.expanduser)?"
code = 'with open("~") as f:\n\tpass'
str1, repr1, str2, repr2 = self.get_exc_as_str(
code, type_)
Expand Down
8 changes: 5 additions & 3 deletions didyoumean/didyoumean_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,12 @@ def get_io_error_sugg(value, frame):

def suggest_if_file_does_not_exist(filename):
""" Suggestions when a file does not exist."""
for f in (os.path.expanduser, os.path.expandvars):
expanded = f(filename)
for func, name in (
(os.path.expanduser, 'os.path.expanduser'),
(os.path.expandvars, 'os.path.expandvars')):
expanded = func(filename)
if os.path.exists(expanded) and filename != expanded:
yield quote(expanded)
yield quote(expanded) + " (calling " + name + ")"


def get_suggestions_for_exception(value, traceback):
Expand Down
8 changes: 6 additions & 2 deletions didyoumean/didyoumean_sugg_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,9 @@ def test_no_such_file_user(self):
code = 'os.listdir("{0}")'
typo, sugg = "~", os.path.expanduser("~")
bad_code, good_code = format_str(code, typo, sugg)
self.throws(bad_code, NOFILE_OS, "'" + sugg + "'")
self.throws(
bad_code, NOFILE_OS,
"'" + sugg + "' (calling os.path.expanduser)")
self.runs(good_code)

def test_no_such_file_vars(self):
Expand All @@ -1297,7 +1299,9 @@ def test_no_such_file_vars(self):
original_home = os.environ.get('HOME', None)
os.environ[key] = sugg
bad_code, good_code = format_str(code, typo, sugg)
self.throws(bad_code, NOFILE_OS, "'" + sugg + "'")
self.throws(
bad_code, NOFILE_OS,
"'" + sugg + "' (calling os.path.expandvars)")
self.runs(good_code)
if original_home is None:
del os.environ[key]
Expand Down
34 changes: 26 additions & 8 deletions didyoumean/readme_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@
"""Code to generate examples in README.md."""
from didyoumean_internal import add_suggestions_to_exception
import sys
import os


def get_exception(code):
"""Helper function to run code and get what it throws."""
""" Helper function to run code and get what it throws. """
try:
exec(code)
except:
return sys.exc_info()
return None


def standardise(string):
""" Replace strings from the environment by the name of the environment
variable. """
for var in ['USER']:
val = os.environ.get(var, None)
if val is not None:
string = string.replace(val, var.lower())
return string


def main():
"""Main."""
# Different examples :
Expand All @@ -35,10 +46,10 @@ def main():
"import math\npi",
],
(3, "Looking for missing imports"): [
"os.getenv",
"string.ascii_lowercase",
],
(4, "Looking in missing imports"): [
"getenv",
"choice",
],
(5, "Special cases"): [
"assert j ** 2 == -1",
Expand Down Expand Up @@ -99,11 +110,18 @@ def main():
"range(999999999999999)",
],
},
(9, (OSError, IOError)): {
(1, "Suggestion for tilde/variable expansions"): [
"os.listdir('~')",
]
}
}

str_func = repr # could be str or repr
for (_, exc_type), exc_examples in sorted(examples.items()):
print("### %s\n" % exc_type.__name__)
for (_, exc_types), exc_examples in sorted(examples.items()):
if not isinstance(exc_types, tuple):
exc_types = (exc_types, )
print("### %s\n" % "/".join(e.__name__ for e in exc_types))
for (_, desc), codes in sorted(exc_examples.items()):
print("##### %s\n" % desc)
for code in codes:
Expand All @@ -113,13 +131,13 @@ def main():
"No exception thrown on this version of Python"
else:
type_, value, traceback = exc
if not issubclass(type_, exc_type):
if not issubclass(type_, exc_types):
before = after = \
"Wrong exception thrown on this version of Python"
else:
before = str_func(value)
before = standardise(str_func(value))
add_suggestions_to_exception(type_, value, traceback)
after = str_func(value)
after = standardise(str_func(value))
if before == after:
after += " (unchanged on this version of Python)"
print("""```python
Expand Down

0 comments on commit dc4a1d2

Please sign in to comment.