1
1
mirror of https://github.com/chubin/cheat.sh.git synced 2024-11-23 19:43:33 +03:00
cheat.sh/lib/cheat_wrapper.py
2018-07-19 09:01:05 +00:00

316 lines
11 KiB
Python

"""
Main cheat.sh wrapper.
Get answers from getters (in get_answer), adds syntax highlighting
or html markup and returns the result.
"""
from gevent.monkey import patch_all
from gevent.subprocess import Popen, PIPE
patch_all()
# pylint: disable=wrong-import-position,wrong-import-order
import sys
import os
import re
import colored
from pygments import highlight as pygments_highlight
from pygments.formatters import Terminal256Formatter # pylint: disable=no-name-in-module
MYDIR = os.path.abspath(os.path.join(__file__, '..', '..'))
sys.path.append("%s/lib/" % MYDIR)
from globals import error, ANSI2HTML, COLOR_STYLES
from buttons import TWITTER_BUTTON, GITHUB_BUTTON, GITHUB_BUTTON_FOOTER
from languages_data import LEXER, LANGUAGE_ALIAS
from get_answer import get_topic_type, get_topics_list, get_answer, find_answer_by_keyword
from beautifier import code_blocks
# import beautifier
# pylint: disable=wrong-import-position,wrong-import-order
ANSI_ESCAPE = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
def remove_ansi(sometext):
"""
Remove ANSI sequences from `sometext` and convert it into plaintext.
"""
return ANSI_ESCAPE.sub('', sometext)
def html_wrapper(data):
"""
Convert ANSI text `data` to HTML
"""
proc = Popen(
["bash", ANSI2HTML, "--palette=solarized", "--bg=dark"],
stdin=PIPE, stdout=PIPE, stderr=PIPE)
data = data.encode('utf-8')
stdout, stderr = proc.communicate(data)
if proc.returncode != 0:
error(stdout + stderr)
return stdout.decode('utf-8')
def _colorize_internal(topic, answer, html_needed):
def _colorize_line(line):
if line.startswith('T'):
line = colored.fg("grey_62") + line + colored.attr('reset')
line = re.sub(r"\{(.*?)\}", colored.fg("orange_3") + r"\1"+colored.fg('grey_35'), line)
return line
line = re.sub(r"\[(F.*?)\]",
colored.bg("black") + colored.fg("cyan") + r"[\1]"+colored.attr('reset'),
line)
line = re.sub(r"\[(g.*?)\]",
colored.bg("dark_gray") \
+ colored.fg("grey_0") \
+ r"[\1]"+colored.attr('reset'),
line)
line = re.sub(r"\{(.*?)\}",
colored.fg("orange_3") + r"\1"+colored.attr('reset'),
line)
line = re.sub(r"<(.*?)>",
colored.fg("cyan") + r"\1"+colored.attr('reset'),
line)
return line
if topic in [':list', ':bash_completion']:
return answer
if topic == ':firstpage-v1':
lines = answer.splitlines()
answer_lines = lines[:9]
answer_lines.append(colored.fg('grey_35')+lines[9]+colored.attr('reset'))
for line in lines[10:]:
answer_lines.append(_colorize_line(line))
if html_needed:
answer_lines = answer_lines[:-2]
answer = "\n".join(answer_lines) + "\n"
return answer
def _colorize_ansi_answer(topic, answer, color_style, # pylint: disable=too-many-arguments
highlight_all=True, highlight_code=False,
unindent_code=False):
color_style = color_style or "native"
lexer_class = LEXER['bash']
if '/' in topic:
section_name = topic.split('/', 1)[0].lower()
section_name = LANGUAGE_ALIAS.get(section_name, section_name)
lexer_class = LEXER.get(section_name, lexer_class)
if section_name == 'php':
answer = "<?\n%s?>\n" % answer
if highlight_all:
highlight = lambda answer: pygments_highlight(
answer, lexer_class(), Terminal256Formatter(style=color_style)).strip('\n')+'\n'
else:
highlight = lambda x: x
if highlight_code:
blocks = code_blocks(answer, wrap_lines=True, unindent_code=(4 if unindent_code else False))
highlighted_blocks = []
for block in blocks:
if block[0] == 1:
this_block = highlight(block[1])
else:
this_block = block[1].strip('\n')+'\n'
highlighted_blocks.append(this_block)
result = "\n".join(highlighted_blocks)
else:
result = highlight(answer).lstrip('\n')
return result
def _github_button(topic_type):
repository = {
"cheat.sheets" : 'chubin/cheat.sheets',
"cheat.sheets dir" : 'chubin/cheat.sheets',
"tldr" : 'tldr-pages/tldr',
"cheat" : 'chrisallenlane/cheat',
"learnxiny" : 'adambard/learnxinyminutes-docs',
"internal" : '',
"search" : '',
"unknown" : '',
}
full_name = repository.get(topic_type, '')
if not full_name:
return ''
short_name = full_name.split('/', 1)[1] # pylint: disable=unused-variable
button = (
"<!-- Place this tag where you want the button to render. -->"
'<a aria-label="Star %(full_name)s on GitHub"'
' data-count-aria-label="# stargazers on GitHub"'
' data-count-api="/repos/%(full_name)s#stargazers_count"'
' data-count-href="/%(full_name)s/stargazers"'
' data-icon="octicon-star"'
' href="https://github.com/%(full_name)s"'
' class="github-button">%(short_name)s</a>'
) % locals()
return button
def _render_html(query, result, editable, repository_button, request_options):
result = result + "\n$"
result = html_wrapper(result)
title = "<title>cheat.sh/%s</title>" % query
# title += ('\n<link rel="stylesheet" href="/files/awesomplete.css" />script'
# ' src="/files/awesomplete.min.js" async></script>')
# submit button: thanks to http://stackoverflow.com/questions/477691/
submit_button = ('<input type="submit" style="position: absolute;'
' left: -9999px; width: 1px; height: 1px;" tabindex="-1" />')
topic_list = ('<datalist id="topics">%s</datalist>'
% ("\n".join("<option value='%s'></option>" % x for x in get_topics_list())))
curl_line = "<span class='pre'>$ curl cheat.sh/</span>"
if query == ':firstpage':
query = ""
form_html = ('<form action="/" method="GET"/>'
'%s%s'
'<input'
' type="text" value="%s" name="topic"'
' list="topics" autofocus autocomplete="off"/>'
'%s'
'</form>') \
% (submit_button, curl_line, query, topic_list)
edit_button = ''
if editable:
# It's possible that topic directory starts with omitted underscore
if '/' in query:
query = '_' + query
edit_page_link = 'https://github.com/chubin/cheat.sheets/edit/master/sheets/' + query
edit_button = (
'<pre style="position:absolute;padding-left:40em;overflow:visible;height:0;">'
'[<a href="%s" style="color:cyan">edit</a>]'
'</pre>') % edit_page_link
result = re.sub("<pre>", edit_button + form_html + "<pre>", result)
result = re.sub("<head>", "<head>" + title, result)
if not request_options.get('quiet'):
result = result.replace('</body>',
TWITTER_BUTTON \
+ GITHUB_BUTTON \
+ repository_button \
+ GITHUB_BUTTON_FOOTER \
+ '</body>')
return result
def _visualize(query, keyword, answers, request_options, html=None): # pylint: disable=too-many-locals
search_mode = bool(keyword)
highlight = not bool(request_options and request_options.get('no-terminal'))
color_style = request_options.get('style', '')
if color_style not in COLOR_STYLES:
color_style = ''
found = True # if the page was found in the database
editable = False # can generated page be edited on github (only cheat.sheets pages can)
result = ""
for topic, answer in answers: # pylint: disable=too-many-nested-blocks
if topic == 'LIMITED':
result += colored.bg('dark_goldenrod') \
+ colored.fg('yellow_1') \
+ ' ' + answer + ' ' \
+ colored.attr('reset') + "\n"
break
topic_type = get_topic_type(topic)
highlight = (highlight
and topic not in [":list", ":bash_completion"]
and topic_type not in ["unknown"]
)
found = found and not topic_type == 'unknown'
editable = editable or topic_type == "cheat.sheets"
if topic_type == "internal" and highlight:
answer = _colorize_internal(topic, answer, html)
else:
answer = _colorize_ansi_answer(
topic, answer, color_style,
highlight_all=highlight,
highlight_code=(topic_type == 'question'
and not request_options.get('add_comments')
and not request_options.get('remove_text')),
unindent_code=request_options.get('unindent_code')
)
if search_mode:
if not highlight:
result += "\n[%s]\n" % topic
else:
result += "\n%s%s %s %s%s\n" % (colored.bg('dark_gray'),
colored.attr("res_underlined"),
topic,
colored.attr("res_underlined"),
colored.attr('reset'))
result += answer
result = result.strip('\n') + "\n"
if search_mode:
editable = False
repository_button = ''
else:
repository_button = _github_button(topic_type)
if html and query:
result = _render_html(
query, result, editable, repository_button, request_options)
return result, found
def _sanitize_query(query):
return re.sub('[<>"]', '', query)
def cheat_wrapper(query, request_options=None, html=False):
"""
Giant megafunction that delivers cheat sheet for `query`.
If `html` is True, the answer is formatted as HTML.
Additional request options specified in `request_options`.
This function is really really bad, and should be rewritten
as soon as possible.
"""
def _strip_hyperlink(query):
return re.sub('(,[0-9]+)+$', '', query)
def _parse_query(query):
topic = query
keyword = None
search_options = ""
keyword = None
if '~' in query:
topic = query
pos = topic.index('~')
keyword = topic[pos+1:]
topic = topic[:pos]
if '/' in keyword:
search_options = keyword[::-1]
search_options = search_options[:search_options.index('/')]
keyword = keyword[:-len(search_options)-1]
return topic, keyword, search_options
query = _sanitize_query(query)
# at the moment, we just remove trailing slashes
# so queries python/ and python are equal
query = _strip_hyperlink(query.rstrip('/'))
topic, keyword, search_options = _parse_query(query)
if keyword:
answers = find_answer_by_keyword(
topic, keyword, options=search_options, request_options=request_options)
else:
answers = [(topic, get_answer(topic, keyword, request_options=request_options))]
return _visualize(query, keyword, answers, request_options, html=html)