{"id":15893860,"url":"https://github.com/chinanf-boy/howdoi-explain","last_synced_at":"2026-01-30T14:16:37.976Z","repository":{"id":90548402,"uuid":"154443083","full_name":"chinanf-boy/howdoi-explain","owner":"chinanf-boy","description":"explain: \u003chowdoi\u003e 解答问题，命令行做起 :heart: explain ✅","archived":false,"fork":false,"pushed_at":"2018-10-24T05:17:12.000Z","size":10,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-13T21:45:00.520Z","etag":null,"topics":["docs","howdoi","zh"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chinanf-boy.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-10-24T05:17:06.000Z","updated_at":"2018-10-30T08:53:27.000Z","dependencies_parsed_at":"2023-07-18T11:00:10.433Z","dependency_job_id":null,"html_url":"https://github.com/chinanf-boy/howdoi-explain","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chinanf-boy/howdoi-explain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fhowdoi-explain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fhowdoi-explain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fhowdoi-explain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fhowdoi-explain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chinanf-boy","download_url":"https://codeload.github.com/chinanf-boy/howdoi-explain/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fhowdoi-explain/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28913958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T12:13:43.263Z","status":"ssl_error","status_checked_at":"2026-01-30T12:13:22.389Z","response_time":66,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docs","howdoi","zh"],"created_at":"2024-10-06T08:13:56.137Z","updated_at":"2026-01-30T14:16:37.959Z","avatar_url":"https://github.com/chinanf-boy.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# gleitz/howdoi [![explain]][source] [![translate-svg]][translate-list]\n\n\u003c!-- [![size-img]][size] --\u003e\n\n[explain]: http://llever.com/explain.svg\n[source]: https://github.com/chinanf-boy/Source-Explain\n[translate-svg]: http://llever.com/translate.svg\n[translate-list]: https://github.com/chinanf-boy/chinese-translate-list\n[size-img]: https://packagephobia.now.sh/badge?p=Name\n[size]: https://packagephobia.now.sh/result?p=Name\n\n「 即时编码, 通过命令行回答 」\n\n---\n\n## explain ✅\n\n\u003c!-- doc-templite START generated --\u003e\n\u003c!-- time = '2018-08-12' --\u003e\n\u003c!-- name = 'gleitz' --\u003e\n\u003c!-- repo = 'howdoi' --\u003e\n\u003c!-- commit = 'f0d9f54b3c7c1879eeb6ecfcc5193ceeb44ad0f2' --\u003e\n版本 | 与日期 | 最新更新 | 更多\n---|---|---|---\n[commit] | ⏰ 2018-08-12 | ![version] | [源码解释][source]\n\n[commit]: https://github.com/gleitz/howdoi/tree/f0d9f54b3c7c1879eeb6ecfcc5193ceeb44ad0f2\n[version]: https://img.shields.io/npm/v/howdoi.svg\n\n\u003c!-- doc-templite END generated --\u003e\n\n### 项目 中文 readme\n\n[Readme](zh.md)\n\n## 生活\n\n[help me live , live need money 💰](https://github.com/chinanf-boy/live-need-money)\n\n---\n\n## 目录\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [requirements.txt](#requirementstxt)\n- [setup.py](#setuppy)\n- [howdoi工作流程](#howdoi%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B)\n  - [howdoi/__init__.py](#howdoi__init__py)\n  - [howdoi/howdoi.py](#howdoihowdoipy)\n    - [python + import](#python--import)\n    - [控制不同Py版本的导入](#%E6%8E%A7%E5%88%B6%E4%B8%8D%E5%90%8Cpy%E7%89%88%E6%9C%AC%E7%9A%84%E5%AF%BC%E5%85%A5)\n    - [各种常量设置和获取](#%E5%90%84%E7%A7%8D%E5%B8%B8%E9%87%8F%E8%AE%BE%E7%BD%AE%E5%92%8C%E8%8E%B7%E5%8F%96)\n    - [拿到requests会话](#%E6%8B%BF%E5%88%B0requests%E4%BC%9A%E8%AF%9D)\n    - [各种工具函数和逻辑函数](#%E5%90%84%E7%A7%8D%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0%E5%92%8C%E9%80%BB%E8%BE%91%E5%87%BD%E6%95%B0)\n      - [get_proxies](#get_proxies)\n      - [_get_result](#_get_result)\n      - [_add_links_to_text](#_add_links_to_text)\n      - [get_text](#get_text)\n      - [_extract_links_from_bing](#_extract_links_from_bing)\n      - [_extract_links_from_google](#_extract_links_from_google)\n      - [_extract_links](#_extract_links)\n      - [_get_search_url](#_get_search_url)\n      - [_get_links](#_get_links)\n      - [get_link_at_pos](#get_link_at_pos)\n      - [_format_output](#_format_output)\n      - [_is_question](#_is_question)\n      - [_get_questions](#_get_questions)\n      - [_get_answer](#_get_answer)\n      - [_get_instructions](#_get_instructions)\n      - [format_answer](#format_answer)\n      - [_enable_cache](#_enable_cache)\n      - [_clear_cache](#_clear_cache)\n      - [howdoi](#howdoi)\n      - [get_parser](#get_parser)\n      - [command_line_runner](#command_line_runner)\n    - [`__main__`](#__main__)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n\n## requirements.txt\n\n依赖管理\n\n``` txt\nPygments==2.2.0\nargparse==1.4.0\ncssselect==1.0.3\nlxml==4.2.4\npyquery==1.4.0\nrequests==2.19.1\nrequests-cache==0.4.13\n```\n\n## setup.py\n\n给予 pip 安装使用的脚本，初看较为麻烦，可看[requests主作者的setup.py指南](https://github.com/kennethreitz/setup.py)\n\n## howdoi工作流程\n\n说说工作流程, \n\n1. 用户运行命令行，带上参数 (主要是需要提问的关键字;如`format date bash`) , [__main__启动了](#__main__)\n2. 然后[command_line_runner](#command_line_runner)开始[解析命令行参数](#get_parser), 并相应分流，主流程是[howdoi 函数](#howdoi)\n3. howdoi 开始它的表演，[获得命令函数\b](#_get_instructions)启动\n\n\u003e 先开始[获得结果链接](#_get_links)\n\n- 3.1\u003e 组合所有常量和需要提问的关键字，将其[变为**Url**](#_get_search_url)\n- 3.2\u003e 使用requests请求库，[请求上述Url, 获得结果返回](#_get_result) {代理之类的设置略}\n- 3.3\u003e 接过上述的html字符串，交由**pyquery**转换操作，[提取](#_extract_links) 搜索引擎(google,bind...) 的每个结果链接\n- 3.4\u003e 至此, [获得结果链接](#_get_links)的操作完成，继续下一步\n\u003e 开始[获取问题](#_get_questions)\n- 3.5\u003e 检查和过滤每个结果链接[是否为解答链接](#_is_question)\n- 3.6\u003e 根据重要的用户参数，要多少答案，对链接进行以下处理\n    - 3.6.1 [计算下第几个链接呢](#get_link_at_pos)\n    - 3.6.2 [请求解答链接，解析html, 获得答案](#_get_answer)，放入答案集合 *\n- 3.7\u003e 答案集合经由[获得命令函数](#_get_instructions)，返回[howdoi](#howdoi)统筹，再返回\n- 3.8\u003e 最后回到[command_line_runner](#command_line_runner) 打印适应的输出结果\n\n\n\u003e **pyquery**，类Jquery语法操作 html\n\n### howdoi/__init__.py\n\npy项目引用的默认目录,初始文件\n\n```py\n__version__ = '1.1.13'\n```\n\n### howdoi/howdoi.py\n\n\n#### python + import\n\n```py\n#!/usr/bin/env python\n\n######################################################\n#\n# howdoi - instant coding answers via the command line\n# written by Benjamin Gleitzman (gleitz@mit.edu)\n# inspired by Rich Jones (rich@anomos.info)\n#\n######################################################\n\nimport argparse\nimport glob\nimport os\nimport random\nimport re\nimport requests\nimport requests_cache\nimport sys\nfrom . import __version__\n\nfrom pygments import highlight\nfrom pygments.lexers import guess_lexer, get_lexer_by_name\nfrom pygments.formatters.terminal import TerminalFormatter\nfrom pygments.util import ClassNotFound\n\nfrom pyquery import PyQuery as pq\nfrom requests.exceptions import ConnectionError\nfrom requests.exceptions import SSLError\n\n```\n\n#### 控制不同Py版本的导入\n\n```py\n# Handle imports for Python 2 and 3\nif sys.version \u003c '3':\n    import codecs\n    from urllib import quote as url_quote\n    from urllib import getproxies\n\n    # Handling Unicode: http://stackoverflow.com/a/6633040/305414\n    def u(x):\n        return codecs.unicode_escape_decode(x)[0]\nelse:\n    from urllib.request import getproxies\n    from urllib.parse import quote as url_quote\n\n    def u(x):\n        return x\n\n\n```\n\n#### 各种常量设置和获取\n\n```py\nif os.getenv('HOWDOI_DISABLE_SSL'):  # Set http instead of https\n    SCHEME = 'http://'\n    VERIFY_SSL_CERTIFICATE = False\nelse:\n    SCHEME = 'https://'\n    VERIFY_SSL_CERTIFICATE = True\n\nURL = os.getenv('HOWDOI_URL') or 'stackoverflow.com'\n\nUSER_AGENTS = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0',\n               'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100 101 Firefox/22.0',\n               'Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko/20100101 Firefox/11.0',\n               ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.5 (KHTML, like Gecko) '\n                'Chrome/19.0.1084.46 Safari/536.5'),\n               ('Mozilla/5.0 (Windows; Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46'\n                'Safari/536.5'), )\nSEARCH_URLS = {\n    'bing': SCHEME + 'www.bing.com/search?q=site:{0}%20{1}',\n    'google': SCHEME + 'www.google.com/search?q=site:{0}%20{1}'\n}\nSTAR_HEADER = u('\\u2605')\nANSWER_HEADER = u('{2}  Answer from {0} {2}\\n{1}')\nNO_ANSWER_MSG = '\u003c no answer given \u003e'\nXDG_CACHE_DIR = os.environ.get('XDG_CACHE_HOME',\n                               os.path.join(os.path.expanduser('~'), '.cache'))\nCACHE_DIR = os.path.join(XDG_CACHE_DIR, 'howdoi')\nCACHE_FILE = os.path.join(CACHE_DIR, 'cache{0}'.format(\n    sys.version_info[0] if sys.version_info[0] == 3 else ''))\n```\n\n#### 拿到requests会话\n\n``` py\nhowdoi_session = requests.session()\n```\n\n#### 各种工具函数和逻辑函数\n\n##### get_proxies\n\n获取代理函数\n\n``` py\ndef get_proxies():\n    proxies = getproxies()\n    filtered_proxies = {}\n    for key, value in proxies.items():\n        if key.startswith('http'):\n            if not value.startswith('http'):\n                filtered_proxies[key] = 'http://%s' % value\n            else:\n                filtered_proxies[key] = value\n    return filtered_proxies\n\n```\n\n##### _get_result\n\nUrl请求函数\n\n``` py\ndef _get_result(url):\n    try:\n        return howdoi_session.get(url, headers={'User-Agent': random.choice(USER_AGENTS)}, proxies=get_proxies(),\n                                  verify=VERIFY_SSL_CERTIFICATE).text\n    except requests.exceptions.SSLError as e:\n        print('[ERROR] Encountered an SSL Error. Try using HTTP instead of '\n              'HTTPS by setting the environment variable \"HOWDOI_DISABLE_SSL\".\\n')\n        raise e\n\n```\n\n##### _add_links_to_text\n\n添加答案的源网址\n\n``` py\ndef _add_links_to_text(element):\n    hyperlinks = element.find('a')\n\n    for hyperlink in hyperlinks:\n        pquery_object = pq(hyperlink)\n        href = hyperlink.attrib['href']\n        copy = pquery_object.text()\n        if (copy == href):\n            replacement = copy\n        else:\n            replacement = \"[{0}]({1})\".format(copy, href)\n        pquery_object.replace_with(replacement)\n```\n\n##### get_text\n\n添加源网址，并答案字符串返回\n\n``` py\ndef get_text(element):\n    ''' return inner text in pyquery element '''\n    _add_links_to_text(element)\n    return element.text(squash_space=False) # 已经不对了，python3.6 没法使用\n    # 直接: return \" \".join(element.text().split())\n\n```\n\n\u003e squash_space=False 是为了，去掉多余空格\n\n##### _extract_links_from_bing\n\n解析bing搜索的结果链接\n\n``` py\ndef _extract_links_from_bing(html):\n    html.remove_namespaces()\n    return [a.attrib['href'] for a in html('.b_algo')('h2')('a')]\n\n```\n\n##### _extract_links_from_google\n\n解析google搜索的结果链接\n\n``` py\ndef _extract_links_from_google(html):\n    return [a.attrib['href'] for a in html('.l')] or \\\n        [a.attrib['href'] for a in html('.r')('a')]\n\n```\n\n##### _extract_links\n\n解析结果链接\n\n``` py\ndef _extract_links(html, search_engine):\n    if search_engine == 'bing':\n        return _extract_links_from_bing(html)\n    return _extract_links_from_google(html)\n\n```\n\n##### _get_search_url\n\n获取搜索Url\n\n``` py\ndef _get_search_url(search_engine):\n    return SEARCH_URLS.get(search_engine, SEARCH_URLS['google'])\n\n```\n\n##### _get_links\n\n获取，解析，搜索引擎「bing,google」的结果链接`\u003ca href\u003e`们\n\n``` py\ndef _get_links(query):\n    search_engine = os.getenv('HOWDOI_SEARCH_ENGINE', 'google')\n    search_url = _get_search_url(search_engine)\n\n    result = _get_result(search_url.format(URL, url_quote(query)))\n    html = pq(result)\n    return _extract_links(html, search_engine)\n\n```\n\n##### get_link_at_pos\n\n用户要求的答案数量，到第几个解答链接了\n\n``` py\ndef get_link_at_pos(links, position):\n    if not links:\n        return False\n\n    if len(links) \u003e= position:\n        link = links[position - 1]\n    else:\n        link = links[-1]\n    return link\n\n```\n\n##### _format_output\n\n格式化输出，带不带颜色\n\n``` py\ndef _format_output(code, args):\n    if not args['color']:\n        return code\n    lexer = None\n\n    # try to find a lexer using the StackOverflow tags\n    # or the query arguments\n    for keyword in args['query'].split() + args['tags']:\n        try:\n            lexer = get_lexer_by_name(keyword)\n            break\n        except ClassNotFound:\n            pass\n\n    # no lexer found above, use the guesser\n    if not lexer:\n        try:\n            lexer = guess_lexer(code)\n        except ClassNotFound:\n            return code\n\n    return highlight(code,\n                     lexer,\n                     TerminalFormatter(bg='dark'))\n\n```\n\n##### _is_question\n\n是不是一个解答链接\n\n``` py\ndef _is_question(link):\n    return re.search('questions/\\d+/', link)\n\n```\n\n##### _get_questions\n\n过滤解答链接\n\n``` py\ndef _get_questions(links):\n    return [link for link in links if _is_question(link)]\n\n```\n\n##### _get_answer\n\n从请求解答链接，返回的html中解析答案，组合\n\n``` py\ndef _get_answer(args, links):\n    link = get_link_at_pos(links, args['pos'])\n    if not link:\n        return False\n    if args.get('link'):\n        return link\n    page = _get_result(link + '?answertab=votes') # 请求解答链接\n    html = pq(page) # 解析html\n\n    first_answer = html('.answer').eq(0) \n\n    instructions = first_answer.find('pre') or first_answer.find('code')\n    args['tags'] = [t.text for t in html('.post-tag')]\n\n    if not instructions and not args['all']:\n        text = get_text(first_answer.find('.post-text').eq(0))\n    elif args['all']:\n        texts = []\n        for html_tag in first_answer.items('.post-text \u003e *'):\n            current_text = get_text(html_tag)\n            if current_text:\n                if html_tag[0].tag in ['pre', 'code']:\n                    texts.append(_format_output(current_text, args))\n                else:\n                    texts.append(current_text)\n        text = '\\n'.join(texts)\n    else:\n        text = _format_output(get_text(instructions.eq(0)), args)\n    if text is None:\n        text = NO_ANSWER_MSG\n    text = text.strip()\n    return text # 获得答案字符串\n\n```\n\n##### _get_instructions\n\n内置问题，解答函数\n\n``` py\ndef _get_instructions(args):\n    links = _get_links(args['query'])\n    if not links:\n        return False\n\n    question_links = _get_questions(links)\n    if not question_links:\n        return False\n\n    only_hyperlinks = args.get('link') # 用户参数， 仅需要解答链接\n    star_headers = (args['num_answers'] \u003e 1 or args['all'])\n\n    answers = []\n    initial_position = args['pos']\n    spliter_length = 80\n    answer_spliter = '\\n' + '=' * spliter_length + '\\n\\n'\n\n    for answer_number in range(args['num_answers']): # 用户要几个答案\n        current_position = answer_number + initial_position\n        args['pos'] = current_position\n        link = get_link_at_pos(question_links, current_position)\n        answer = _get_answer(args, question_links)\n        if not answer:\n            continue\n        if not only_hyperlinks: # 是否仅输出解答链接\n            answer = format_answer(link, answer, star_headers)\n        answer += '\\n'\n        answers.append(answer) # 答案集合\n    return answer_spliter.join(answers)\n\n```\n\n##### format_answer\n\n格式化答案\n\n``` py\ndef format_answer(link, answer, star_headers):\n    if star_headers:\n        return ANSWER_HEADER.format(link, answer, STAR_HEADER)\n    return answer\n\n```\n\n##### _enable_cache\n\n缓存缓存\n\n``` py\ndef _enable_cache():\n    if not os.path.exists(CACHE_DIR):\n        os.makedirs(CACHE_DIR)\n    requests_cache.install_cache(CACHE_FILE)\n\n```\n\n##### _clear_cache\n\n删除缓存\n\n``` py\ndef _clear_cache():\n    for cache in glob.iglob('{0}*'.format(CACHE_FILE)):\n        os.remove(cache)\n\n```\n\n##### howdoi\n\n开始，获取解答命令，和一些异常输出\n\n``` py\ndef howdoi(args):\n    args['query'] = ' '.join(args['query']).replace('?', '')\n    try:\n        return _get_instructions(args) or 'Sorry, couldn\\'t find any help with that topic\\n'\n    except (ConnectionError, SSLError):\n        return 'Failed to establish network connection\\n'\n\n```\n\n##### get_parser\n\n命令行参数解析\n\n``` py\ndef get_parser():\n    parser = argparse.ArgumentParser(description='instant coding answers via the command line')\n    parser.add_argument('query', metavar='QUERY', type=str, nargs='*',\n                        help='the question to answer')\n    parser.add_argument('-p', '--pos', help='select answer in specified position (default: 1)', default=1, type=int)\n    parser.add_argument('-a', '--all', help='display the full text of the answer',\n                        action='store_true')\n    parser.add_argument('-l', '--link', help='display only the answer link',\n                        action='store_true')\n    parser.add_argument('-c', '--color', help='enable colorized output',\n                        action='store_true')\n    parser.add_argument('-n', '--num-answers', help='number of answers to return', default=1, type=int)\n    parser.add_argument('-C', '--clear-cache', help='clear the cache',\n                        action='store_true')\n    parser.add_argument('-v', '--version', help='displays the current version of howdoi',\n                        action='store_true')\n    return parser\n\n```\n\n##### command_line_runner\n\n有始有终的命令运行器\n\n``` py\ndef command_line_runner():\n    parser = get_parser() # 始\n    args = vars(parser.parse_args())\n\n    if args['version']:\n        print(__version__)\n        return\n\n    if args['clear_cache']:\n        _clear_cache()\n        print('Cache cleared successfully')\n        return\n\n    if not args['query']:\n        parser.print_help()\n        return\n\n    # enable the cache if user doesn't want it to be disabled\n    if not os.getenv('HOWDOI_DISABLE_CACHE'):\n        _enable_cache()\n\n    if os.getenv('HOWDOI_COLORIZE'):\n        args['color'] = True\n\n    utf8_result = howdoi(args).encode('utf-8', 'ignore')\n    if sys.version \u003c '3':\n        print(utf8_result)\n    else:\n        # Write UTF-8 to stdout: https://stackoverflow.com/a/3603160\n        sys.stdout.buffer.write(utf8_result)\n    # close the session to release connection\n    howdoi_session.close() # 终\n```\n\n#### `__main__`\n\n此文件作为直接运行时，触发\n\n``` py\nif __name__ == '__main__':\n    command_line_runner()\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchinanf-boy%2Fhowdoi-explain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchinanf-boy%2Fhowdoi-explain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchinanf-boy%2Fhowdoi-explain/lists"}