Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/shobrook/rebound
Browse files Browse the repository at this point in the history
  • Loading branch information
shobrook committed Dec 4, 2018
2 parents 5507def + 13bf183 commit 61a1abd
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 10 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# rebound

[![Downloads](http://pepy.tech/badge/rebound-cli)](http://pepy.tech/count/rebound-cli)

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.

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

## Installation

>Requires Python 3.0 or higher.
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:

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

`$ sudo apt-get install rebound-cli`

Requires Python 3.0 or higher.

## Usage

Running a file with `rebound` is just as easy as compiling it normally:
Expand Down
39 changes: 34 additions & 5 deletions rebound/rebound.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def get_error_message(error, language):
if any(e in error for e in ["KeyboardInterrupt", "SystemExit", "GeneratorExit"]): # Non-compiler errors
return None
else:
return error.split('\n')[-2][1:]
return error.split('\n')[-2].strip()
elif language == "node":
return error.split('\n')[4][1:]
elif language == "go run":
Expand Down Expand Up @@ -245,7 +245,7 @@ def get_search_results(soup):
search_results = []

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

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

def souper(url):
"""Turns a given URL into a BeautifulSoup object."""
html = requests.get(url, headers={"User-Agent": random.choice(USER_AGENTS)})

try:
html = requests.get(url, headers={"User-Agent": random.choice(USER_AGENTS)})
except requests.exceptions.RequestException:
sys.stdout.write("\n%s%s%s" % (RED, "Rebound was unable to fetch Stack Overflow results. "
"Please check that you are connected to the internet.\n", END))
sys.exit(1)

if re.search("\.com/nocaptcha", html.url): # URL is a captcha page
return None
Expand Down Expand Up @@ -342,6 +348,7 @@ def __init__(self, widget):
self._forward_keypress = None
self._old_cursor_coords = None
self._rows_max_cached = 0
self._rows_max_displayable = 0
self.__super.__init__(widget)


Expand All @@ -363,7 +370,7 @@ def render(self, size, focus=False):
fill_height = maxrow - canv_rows
if fill_height > 0: # Canvas is lower than available vertical space
canv.pad_trim_top_bottom(0, fill_height)

self._rows_max_displayable = maxrow
if canv_cols <= maxcol and canv_rows <= maxrow: # Canvas is small enough to fit without trimming
return canv

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

@property
def scroll_ratio(self):
return self._rows_max_cached / self._rows_max_displayable

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


def render(self, size, focus=False):
Expand Down Expand Up @@ -622,6 +633,12 @@ def is_scrolling_widget(w):
if is_scrolling_widget(w):
return w

@property
def scrollbar_column(self):
if self.scrollbar_side == SCROLLBAR_LEFT:
return 0
if self.scrollbar_side == SCROLLBAR_RIGHT:
return self._original_widget_size[0]

def keypress(self, size, key):
return self._original_widget.keypress(self._original_widget_size, key)
Expand All @@ -644,6 +661,18 @@ def mouse_event(self, size, event, button, col, row, focus):
pos = ow.get_scrollpos(ow_size)
ow.set_scrollpos(pos + 1)
return True
elif col == self.scrollbar_column:
ow.set_scrollpos(int(row*ow.scroll_ratio))
if event == "mouse press":
self._dragging = True
elif event == "mouse release":
self._dragging = False
elif self._dragging:
ow.set_scrollpos(int(row*ow.scroll_ratio))
if event == "mouse release":
self._dragging = False



return False

Expand Down Expand Up @@ -714,7 +743,7 @@ def _handle_input(self, input):

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

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"License :: OSI Approved :: MIT License",
"Programming Language :: Python"
],
keywords="stackoverflow stack overflow debug debugging error-handling compile errors error message cli",
keywords="stackoverflow stack overflow debug debugging error-handling compile errors error message cli search commandline",
include_package_data=True,
packages=["rebound"],
entry_points={"console_scripts": ["rebound = rebound.rebound:main"]},
Expand Down
Empty file added tests/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions tests/python_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import pytest
import traceback
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname( __file__ ), "..", "rebound"))
import rebound

# Constants and helper functions
EXCEPTION_DETAILS = "Exception details"

def gen_python_exception(exception_type):
stack_trace = None
try:
raise exception_type(EXCEPTION_DETAILS)
except Exception:
stack_trace = traceback.format_exc()
return stack_trace

def gen_expected_message(exception_type_str):
return exception_type_str + ": " + EXCEPTION_DETAILS

# Tests
@pytest.mark.parametrize("exception_type, exception_type_str", [
(StopIteration, "StopIteration"),
(StopAsyncIteration, "StopAsyncIteration"),
(ArithmeticError, "ArithmeticError"),
(AssertionError, "AssertionError"),
(AttributeError, "AttributeError"),
(BufferError, "BufferError"),
(EOFError, "EOFError"),
(ImportError, "ImportError"),
(MemoryError, "MemoryError"),
(NameError, "NameError"),
(OSError, "OSError"),
(ReferenceError, "ReferenceError"),
(RuntimeError, "RuntimeError"),
(SyntaxError, "SyntaxError"),
(SystemError, "SystemError"),
(TypeError, "TypeError"),
(ValueError, "ValueError"),
(Warning, "Warning")
])
def test_get_error_message(exception_type, exception_type_str):
error_message = rebound.get_error_message(gen_python_exception(exception_type), "python3")
expected_error_message = gen_expected_message(exception_type_str)
assert error_message == expected_error_message

0 comments on commit 61a1abd

Please sign in to comment.