mirror of
https://github.com/chubin/cheat.sh.git
synced 2024-11-23 19:43:33 +03:00
316 lines
11 KiB
Python
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)
|