Skip to content

Commit 61a1abd

Browse files
committed
2 parents 5507def + 13bf183 commit 61a1abd

File tree

5 files changed

+83
-10
lines changed

5 files changed

+83
-10
lines changed

README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# rebound
22

3-
[![Downloads](http://pepy.tech/badge/rebound-cli)](http://pepy.tech/count/rebound-cli)
4-
53
Rebound is a command-line tool that instantly fetches Stack Overflow results when you get a compiler error. Just use the `rebound` command to execute your file.
64

75
![Placeholder Demo](docs/demo.gif)
@@ -10,6 +8,8 @@ __Featured in:__ [50 Most Popular Python Projects in 2018](https://boostlog.io/@
108

119
## Installation
1210

11+
>Requires Python 3.0 or higher.
12+
1313
Rebound works on MacOS, Linux, and Windows (if you use Cygwin), with binary downloads available for [every release.](https://github.com/shobrook/rebound/releases) You can also install it with pip:
1414

1515
`$ pip install rebound-cli`
@@ -18,8 +18,6 @@ or apt-get if you're using Linux:
1818

1919
`$ sudo apt-get install rebound-cli`
2020

21-
Requires Python 3.0 or higher.
22-
2321
## Usage
2422

2523
Running a file with `rebound` is just as easy as compiling it normally:

rebound/rebound.py

+34-5
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def get_error_message(error, language):
107107
if any(e in error for e in ["KeyboardInterrupt", "SystemExit", "GeneratorExit"]): # Non-compiler errors
108108
return None
109109
else:
110-
return error.split('\n')[-2][1:]
110+
return error.split('\n')[-2].strip()
111111
elif language == "node":
112112
return error.split('\n')[4][1:]
113113
elif language == "go run":
@@ -245,7 +245,7 @@ def get_search_results(soup):
245245
search_results = []
246246

247247
for result in soup.find_all("div", class_="question-summary search-result"):
248-
title_container = result.find_all("div", class_="result-link")[0].find_all("span")[0].find_all("a")[0]
248+
title_container = result.find_all("div", class_="result-link")[0].find_all("a")[0]
249249

250250
if result.find_all("div", class_="status answered") != []: # Has answers
251251
answer_count = int(result.find_all("div", class_="status answered")[0].find_all("strong")[0].text)
@@ -267,7 +267,13 @@ def get_search_results(soup):
267267

268268
def souper(url):
269269
"""Turns a given URL into a BeautifulSoup object."""
270-
html = requests.get(url, headers={"User-Agent": random.choice(USER_AGENTS)})
270+
271+
try:
272+
html = requests.get(url, headers={"User-Agent": random.choice(USER_AGENTS)})
273+
except requests.exceptions.RequestException:
274+
sys.stdout.write("\n%s%s%s" % (RED, "Rebound was unable to fetch Stack Overflow results. "
275+
"Please check that you are connected to the internet.\n", END))
276+
sys.exit(1)
271277

272278
if re.search("\.com/nocaptcha", html.url): # URL is a captcha page
273279
return None
@@ -342,6 +348,7 @@ def __init__(self, widget):
342348
self._forward_keypress = None
343349
self._old_cursor_coords = None
344350
self._rows_max_cached = 0
351+
self._rows_max_displayable = 0
345352
self.__super.__init__(widget)
346353

347354

@@ -363,7 +370,7 @@ def render(self, size, focus=False):
363370
fill_height = maxrow - canv_rows
364371
if fill_height > 0: # Canvas is lower than available vertical space
365372
canv.pad_trim_top_bottom(0, fill_height)
366-
373+
self._rows_max_displayable = maxrow
367374
if canv_cols <= maxcol and canv_rows <= maxrow: # Canvas is small enough to fit without trimming
368375
return canv
369376

@@ -510,6 +517,9 @@ def rows_max(self, size=None, focus=False):
510517
raise RuntimeError("Not a flow/box widget: %r" % self._original_widget)
511518
return self._rows_max_cached
512519

520+
@property
521+
def scroll_ratio(self):
522+
return self._rows_max_cached / self._rows_max_displayable
513523

514524
class ScrollBar(urwid.WidgetDecoration):
515525
# TODO: Change scrollbar size and color(?)
@@ -531,6 +541,7 @@ def __init__(self, widget, thumb_char=u'\u2588', trough_char=' ',
531541
self.scrollbar_side = side
532542
self.scrollbar_width = max(1, width)
533543
self._original_widget_size = (0, 0)
544+
self._dragging = False
534545

535546

536547
def render(self, size, focus=False):
@@ -622,6 +633,12 @@ def is_scrolling_widget(w):
622633
if is_scrolling_widget(w):
623634
return w
624635

636+
@property
637+
def scrollbar_column(self):
638+
if self.scrollbar_side == SCROLLBAR_LEFT:
639+
return 0
640+
if self.scrollbar_side == SCROLLBAR_RIGHT:
641+
return self._original_widget_size[0]
625642

626643
def keypress(self, size, key):
627644
return self._original_widget.keypress(self._original_widget_size, key)
@@ -644,6 +661,18 @@ def mouse_event(self, size, event, button, col, row, focus):
644661
pos = ow.get_scrollpos(ow_size)
645662
ow.set_scrollpos(pos + 1)
646663
return True
664+
elif col == self.scrollbar_column:
665+
ow.set_scrollpos(int(row*ow.scroll_ratio))
666+
if event == "mouse press":
667+
self._dragging = True
668+
elif event == "mouse release":
669+
self._dragging = False
670+
elif self._dragging:
671+
ow.set_scrollpos(int(row*ow.scroll_ratio))
672+
if event == "mouse release":
673+
self._dragging = False
674+
675+
647676

648677
return False
649678

@@ -714,7 +743,7 @@ def _handle_input(self, input):
714743

715744
pile = urwid.Pile(self._stylize_question(question_title, question_desc, question_stats) + [urwid.Divider('*')] +
716745
interleave(answers, [urwid.Divider('-')] * (len(answers) - 1)))
717-
padding = urwid.Padding(ScrollBar(Scrollable(pile)), left=2, right=2)
746+
padding = ScrollBar(Scrollable(urwid.Padding(pile, left=2, right=2)))
718747
#filler = urwid.Filler(padding, valign="top")
719748
linebox = urwid.LineBox(padding)
720749

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"License :: OSI Approved :: MIT License",
3131
"Programming Language :: Python"
3232
],
33-
keywords="stackoverflow stack overflow debug debugging error-handling compile errors error message cli",
33+
keywords="stackoverflow stack overflow debug debugging error-handling compile errors error message cli search commandline",
3434
include_package_data=True,
3535
packages=["rebound"],
3636
entry_points={"console_scripts": ["rebound = rebound.rebound:main"]},

tests/__init__.py

Whitespace-only changes.

tests/python_test.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import pytest
2+
import traceback
3+
import sys
4+
import os
5+
sys.path.insert(0, os.path.join(os.path.dirname( __file__ ), "..", "rebound"))
6+
import rebound
7+
8+
# Constants and helper functions
9+
EXCEPTION_DETAILS = "Exception details"
10+
11+
def gen_python_exception(exception_type):
12+
stack_trace = None
13+
try:
14+
raise exception_type(EXCEPTION_DETAILS)
15+
except Exception:
16+
stack_trace = traceback.format_exc()
17+
return stack_trace
18+
19+
def gen_expected_message(exception_type_str):
20+
return exception_type_str + ": " + EXCEPTION_DETAILS
21+
22+
# Tests
23+
@pytest.mark.parametrize("exception_type, exception_type_str", [
24+
(StopIteration, "StopIteration"),
25+
(StopAsyncIteration, "StopAsyncIteration"),
26+
(ArithmeticError, "ArithmeticError"),
27+
(AssertionError, "AssertionError"),
28+
(AttributeError, "AttributeError"),
29+
(BufferError, "BufferError"),
30+
(EOFError, "EOFError"),
31+
(ImportError, "ImportError"),
32+
(MemoryError, "MemoryError"),
33+
(NameError, "NameError"),
34+
(OSError, "OSError"),
35+
(ReferenceError, "ReferenceError"),
36+
(RuntimeError, "RuntimeError"),
37+
(SyntaxError, "SyntaxError"),
38+
(SystemError, "SystemError"),
39+
(TypeError, "TypeError"),
40+
(ValueError, "ValueError"),
41+
(Warning, "Warning")
42+
])
43+
def test_get_error_message(exception_type, exception_type_str):
44+
error_message = rebound.get_error_message(gen_python_exception(exception_type), "python3")
45+
expected_error_message = gen_expected_message(exception_type_str)
46+
assert error_message == expected_error_message

0 commit comments

Comments
 (0)