diff --git a/lib/adapter/question.py b/lib/adapter/question.py index eee6539..3dd7f09 100644 --- a/lib/adapter/question.py +++ b/lib/adapter/question.py @@ -4,11 +4,14 @@ Configuration parameters: path.internal.bin """ +# pylint: disable=relative-import,wrong-import-position,wrong-import-order + +from __future__ import print_function + from gevent.monkey import patch_all from gevent.subprocess import Popen, PIPE patch_all() -import sys import os import re @@ -16,10 +19,20 @@ from polyglot.detect import Detector from polyglot.detect.base import UnknownLanguage from config import CONFIG -from adapter import Adapter +from upstream import UpstreamAdapter from languages_data import SO_NAME -class Question(Adapter): +class Question(UpstreamAdapter): + + """ + Answer to a programming language question, using Stackoverflow + as the main data source. Heavy lifting is done by an external + program `CONFIG["path.internal.bin.upstream"]`. + + If the program is not found, fallback to the superclass `UpstreamAdapter`, + which queries the upstream server (by default https://cheat.sh/) + fot the answer + """ _adapter_name = "question" _output_format = "text+code" @@ -30,6 +43,11 @@ class Question(Adapter): Find answer for the `topic` question. """ + if not os.path.exists(CONFIG["path.internal.bin.upstream"]): + # if the upstream program is not found, use normal upstream adapter + self._output_format = "ansi" + return UpstreamAdapter._get_page(self, topic, request_options=request_options) + # if there is a language name in the section name, # cut it off (de:python => python) if '/' in topic: @@ -55,7 +73,8 @@ class Question(Adapter): query_text = re.sub('/[0-9]+$', '', query_text) detector = Detector(query_text) supposed_lang = detector.languages[0].code - if len(topic_words) > 2 or supposed_lang in ['az', 'ru', 'uk', 'de', 'fr', 'es', 'it', 'nl']: + if len(topic_words) > 2 \ + or supposed_lang in ['az', 'ru', 'uk', 'de', 'fr', 'es', 'it', 'nl']: lang = supposed_lang if supposed_lang.startswith('zh_') or supposed_lang == 'zh': lang = 'zh' @@ -72,7 +91,7 @@ class Question(Adapter): else: topic = [topic] - cmd = [os.path.join(CONFIG["path.internal.bin"], "bin/get-answer-for-question")] + topic + cmd = [CONFIG["path.internal.bin.upstream"]] + topic proc = Popen(cmd, stdout=PIPE, stderr=PIPE) answer = proc.communicate()[0].decode('utf-8') return answer diff --git a/lib/adapter/upstream.py b/lib/adapter/upstream.py new file mode 100644 index 0000000..8625748 --- /dev/null +++ b/lib/adapter/upstream.py @@ -0,0 +1,67 @@ +""" +Adapter for an external cheat sheets service (i.e. for cheat.sh) + +Configuration parameters: + + upstream.url + upstream.timeout +""" + +# pylint: disable=relative-import + +import textwrap +import requests + +from config import CONFIG +from adapter import Adapter + +def _are_you_offline(): + return textwrap.dedent( + """ + . + Are you offline? + _________________ + | | ___________ |o| Though it could be theoretically possible + | | ___________ | | to use cheat.sh fully offline, + | | ___________ | | and for *programming languages questions* too, + | | ___________ | | this very feature is not yet implemented. + | |_____________| | + | _______ | If you find it useful, please visit + | | | || https://github.com/chubin/issues/140 + | DD | | V| and drop a couple of lines to encourage + |____|_______|____| the authors to develop it as soon as possible + + . + """) + +class UpstreamAdapter(Adapter): + + """ + Connect to the upstream server `CONFIG["upstream.url"]` and fetch + response from it. The response is supposed to have the "ansi" format. + If the server does not respond within `CONFIG["upstream.timeout"]` seconds, + or if a connection error occurs, the "are you offline" banner is displayed. + + Answers are by default cached; the failure answer is marked with the no-cache + property ("cache": False). + """ + + _adapter_name = "upstream" + _output_format = "ansi" + _cache_needed = True + + def _get_page(self, topic, request_options=None): + + options_string = "&".join(["%s=%s" % (x, y) for (x, y) in request_options.items()]) + url = CONFIG["upstream.url"].rstrip('/') \ + + '/' + topic.lstrip('/') \ + + "?" + options_string + try: + response = requests.get(url, timeout=CONFIG["upstream.timeout"]) + answer = response.text + except requests.exceptions.ConnectionError: + answer = {"cache": False, "answer":_are_you_offline()} + return answer + + def _get_list(self, prefix=None): + return [] diff --git a/lib/config.py b/lib/config.py index 17fd5e1..840595f 100644 --- a/lib/config.py +++ b/lib/config.py @@ -94,6 +94,7 @@ _CONFIG = { "log.level": 4, "path.internal.ansi2html": os.path.join(_MYDIR, "share/ansi2html.sh"), "path.internal.bin": os.path.join(_MYDIR, "bin"), + "path.internal.bin.upstream": os.path.join(_MYDIR, "bin", "upstream"), "path.internal.malformed": os.path.join(_MYDIR, "share/static/malformed-response.html"), "path.internal.pages": os.path.join(_MYDIR, "share"), "path.internal.static": os.path.join(_MYDIR, "share/static"), @@ -123,6 +124,8 @@ _CONFIG = { ("^[^/]*$", "unknown"), ("^[a-z][a-z]-[a-z][a-z]$", "translation"), ], + "upstream.url": "https://cheat.sh", + "upstream.timeout": 5, "search.limit": 20, "server.bind": "0.0.0.0", "server.port": 8002,