././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1664818366.110763 howdoi-2.0.20/0000755000076500000240000000000014316616276012321 5ustar00gleitzstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818222.0 howdoi-2.0.20/CHANGES.txt0000644000076500000240000001060214316616056014125 0ustar00gleitzstaff2.0.20 ------ - Update dependency versions - Add support for Python 3.10 2.0.19 ------ - Fix typo 2.0.18 ------ - Fixed issue with howdoi cache where cache misses would be printed to the console 2.0.17 ------ - New documentation and mkdocs - Fixed issue with how howdoi chooses the proper search engine (command line flags now override environment variables) - Added a search engine fallback if one of the search engines fails - Fixed issue with howdoi cache 2.0.16 ------ - Fix GDPR issue for those using howdoi in countries outside the US - Better support for using `HOWDOI_URL` 2.0.15 ------ - Add explainability with `-x` or `--explain` options - Better error checking for when search engines block queries - Using improved DuckDuckGo endpoint - Answer pages now fetched in parallel for speed improvement 2.0.14 ------ - Fix a number of bugs by switching from parsing Google links to looking for URLs instead 2.0.13 ------ - More permanent fix for extracting Google links 2.0.12 ------ - Hotfix for Google link formatting 2.0.11 ------ - Hotfix for Google link formatting 2.0.10 ------ - Hotfix for new Google classnames - Separate requirements.txt files for prod and dev 2.0.9 ------ - Cleaner command line options that also include environment variables - README updates 2.0.8 ------ - Fix issue for answers that have no code in the answer but code in the comments - Add range checks for -n and -p flags - Moved from Travis to Github Actions - Dropped Python 2.7 support 2.0.7 ------ - Update for new Google CSS style 2.0.6 ------ - Fix issue where `-a` would not return a proper response due to updated CSS on StackOverflow 2.0.5 ------ - New logo and colors! 2.0.4 ------ - Cachelib rollback to support Python 2.7 - Better error message when Google is being blocked (for example in China) 2.0.3 ------ - Bring back Python 2.7 support (for now) 2.0.2 ------ - Fixed keep support for stashing and viewing answers 2.0.1 ------ - Added JSON output with the -j flag (great for consuming howdoi results for use in other apps) - Added stashing ability for saving useful answer for later (based on https://github.com/OrkoHunter/keep) - Added caching for tests to prevent being rate limited by Google while developing - Added easier method for calling howdoi when imported (howdoi.howdoi) 1.2.1 ------ - Fix dependency issue 1.2.0 ------ - Massive speed improvements of startup, answer fetching, and caching - Command line flags for alternate search engines - Remove duplicate answers 1.1.14 ------ - Links displayed with markdown syntax - Improved performance and caching (again) 1.1.13 ------ - Improved performance and caching - More friendly answer display - Added support for Python 3.6 - Removed support for Python 2.6 1.1.12 ------ - Add additional search engine support 1.1.11 ------ - Fix issue with UTF-8 encoding 1.1.10 ------ - Include the link in output when asking for >1 answer - Compatibility with linuxbrew 1.1.9 ------ - Fix issue with upload to PyPI 1.1.8 ------ - Fix colorization when HOWDOI_COLORIZE env variable is enabled - Fix certificate validation when SSL disabled 1.1.7 ------ - Add Localization support with HOWDOI_LOCALIZATION env variable (Currently only pt-br and en) 1.1.6 ------ - Updates for Python3 - Updates for caching 1.1.5 ------ - Updates for Python3 - Fix issues with cache - Allow disabling SSL when accessing Google 1.1.4 ------ - Added caching 1.1.3 ------ - Added fix to handle change in Google search page HTML - Updated Travis CI tests 1.1.2 ------ - Compatibility fixes for Python3.2 - Travis CI tests now being run for Python 2.6, 2.7, 3.2, and 3.3 1.1.1 ------ - Added message when question has no answer 1.1 ------ - Added multiple answers with -n/--num-answers flag - Added colorized output with -c/--color flag - Added answer link to the bottom of questions with -a/--all flag - Unit tests now managed through Travis CI 1.0 ------ - Added support for Python3 - Switched to the requests library instead of urllib2 - Project status changed to Production/Stable - Added troubleshooting steps to the README 0.2 ------ - Added sane flags - Now using ``/usr/bin/env python`` instead of ``/usr/bin/python`` - Updated README for brew installation instructions 0.1.2 ------ - Added Windows executable - Updated README for pip installation instructions 0.1.1 ------ - Added to PyPI 0.1 ------ - We're doing it live! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1624472669.0 howdoi-2.0.20/LICENSE.txt0000644000076500000240000000206714064676135014151 0ustar00gleitzstaffCopyright (C) 2012 Benjamin Gleitzman (gleitz@mit.edu) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1631227644.0 howdoi-2.0.20/MANIFEST.in0000644000076500000240000000030514116507374014051 0ustar00gleitzstaffinclude LICENSE.txt include README.md include CHANGES.txt include fastentrypoints.py include requirements.txt include test_howdoi.py exclude howdoi.rb exclude .*rc prune page_cache prune notebooks ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1664818366.1103442 howdoi-2.0.20/PKG-INFO0000644000076500000240000003715714316616276013433 0ustar00gleitzstaffMetadata-Version: 2.1 Name: howdoi Version: 2.0.20 Summary: Instant coding answers via the command line Home-page: https://github.com/gleitz/howdoi Author: Benjamin Gleitzman Author-email: gleitz@mit.edu Maintainer: Benjamin Gleitzman Maintainer-email: gleitz@mit.edu License: MIT Description:

Sherlock, your neighborhood command-line sloth sleuth

howdoi

Instant coding answers via the command line

⚡ Never open your browser to look for help again ⚡

build status downloads Python versions

------------------------------------------------------------------------ ## Introduction to howdoi Are you a hack programmer? Do you find yourself constantly Googling for how to do basic programming tasks? Suppose you want to know how to format a date in bash. Why open your browser and read through blogs (risking major distraction) when you can simply stay in the console and ask howdoi: $ howdoi format date bash > DATE=`date +%Y-%m-%d` howdoi will answer all sorts of queries: $ howdoi print stack trace python > import traceback > > try: > 1/0 > except: > print '>>> traceback <<<' > traceback.print_exc() > print '>>> end of traceback <<<' > traceback.print_exc() $ howdoi convert mp4 to animated gif > video=/path/to/video.avi > outdir=/path/to/output.gif > mplayer "$video" \ > -ao null \ > -ss "00:01:00" \ # starting point > -endpos 10 \ # duration in second > -vo gif89a:fps=13:output=$outdir \ > -vf scale=240:180 $ howdoi create tar archive > tar -cf backup.tar --exclude "www/subf3" www [![image](http://imgs.xkcd.com/comics/tar.png)](https://xkcd.com/1168/) ## Installation pip install howdoi ## Usage ### New to howdoi? howdoi howdoi ### RTFM - [Introduction and installation](http://gleitz.github.io/howdoi/introduction/) - [Usage](http://gleitz.github.io/howdoi/usage/) - [Contributing to howdoi](http://gleitz.github.io/howdoi/contributing_to_howdoi/) - [Advanced usage](http://gleitz.github.io/howdoi/howdoi_advanced_usage/) - [Troubleshooting](http://gleitz.github.io/howdoi/troubleshooting/) ### Commands usage: howdoi [-h] [-p POS] [-n NUM] [-a] [-l] [-c] [-x] [-C] [-j] [-v] [-e [ENGINE]] [--save] [--view] [--remove] [--empty] [QUERY ...] instant coding answers via the command line positional arguments: QUERY the question to answer optional arguments: -h, --help show this help message and exit -p POS, --pos POS select answer in specified position (default: 1) -n NUM, --num NUM number of answers to return (default: 1) -a, --all display the full text of the answer -l, --link display only the answer link -c, --color enable colorized output -x, --explain explain how answer was chosen -C, --clear-cache clear the cache -j, --json return answers in raw json format -v, --version display the current version of howdoi -e [ENGINE], --engine [ENGINE] search engine for this query (google, bing, duckduckgo) --save, --stash stash a howdoi answer --view view your stash --remove remove an entry in your stash --empty empty your stash environment variable examples: HOWDOI_COLORIZE=1 HOWDOI_DISABLE_CACHE=1 HOWDOI_DISABLE_SSL=1 HOWDOI_SEARCH_ENGINE=google HOWDOI_URL=serverfault.com Using the howdoi stashing feature (for more advanced features view the [keep documentation](https://github.com/OrkoHunter/keep)). stashing: howdoi --save QUERY viewing: howdoi --view removing: howdoi --remove (will be prompted which answer to delete) emptying: howdoi --empty (empties entire stash, will be prompted to confirm) As a shortcut, if you commonly use the same parameters each time and don\'t want to type them, add something similar to your .bash_profile (or otherwise). This example gives you 5 colored results each time. alias h='function hdi(){ howdoi $* -c -n 5; }; hdi' And then to run it from the command line simply type: $ h format date bash You can also search other [StackExchange properties](https://stackexchange.com/sites#traffic) for answers: HOWDOI_URL=cooking.stackexchange.com howdoi make pesto or as an alias: alias hcook='function hcook(){ HOWDOI_URL=cooking.stackexchange.com howdoi $* ; }; hcook' hcook make pesto Other useful aliases: alias hless='function hdi(){ howdoi $* -c | less --raw-control-chars --quit-if-one-screen --no-init; }; hdi' ## Contributors - Benjamin Gleitzman ([\@gleitz](http://twitter.com/gleitz)) - Yanlam Ko ([\@YKo20010](https://github.com/YKo20010)) - Diana Arreola ([\@diarreola](https://github.com/diarreola)) - Eyitayo Ogunbiyi ([\@tayoogunbiyi](https://github.com/tayoogunbiyi)) - Chris Nguyen ([\@chrisngyn](https://github.com/chrisngyn)) - Shageldi Ovezov ([\@ovezovs](https://github.com/chrisngyn)) - Mwiza Simbeye ([\@mwizasimbeye11](https://github.com/mwizasimbeye11)) - Shantanu Verma ([\@SaurusXI](https://github.com/SaurusXI)) - Sheza Munir ([\@ShezaMunir](https://github.com/ShezaMunir)) - Jyoti Bisht ([\@joeyouss](https://github.com/joeyouss)) - And [more!](https://github.com/gleitz/howdoi/graphs/contributors) ## How to contribute We welcome contributions that make howdoi better and improve the existing functionalities of the project. We have created a separate [guide to contributing to howdoi](http://gleitz.github.io/howdoi/contributing_to_howdoi/) that explains how to get up and running with your first pull request. ## Notes - Works with Python 3.5 and newer. Unfortunately Python 2.7 support has been discontinued :( - There is a [GUI that wraps howdoi](https://pypi.org/project/pysimplegui-howdoi/) - There is a [Flask webapp that wraps howdoi](https://howdoi.maxbridgland.com) - An [Alfred Workflow](http://blog.gleitzman.com/post/48539944559/howdoi-alfred-even-more-instant-answers) for howdoi - Slack integration available through [slack-howdoi](https://github.com/ellisonleao/slack-howdoi) - Telegram integration available through [howdoi-telegram](https://github.com/aahnik/howdoi-telegram) - Special thanks to Rich Jones ([\@miserlou](https://github.com/miserlou)) for the idea - More thanks to [Ben Bronstein](https://benbronstein.com/) for the logo ## Visual Studio Code Extension Installation Head over to the [MarketPlace](https://marketplace.visualstudio.com/items?itemName=howdoi-org.howdoi) to install the extension. # News 2.0.20 ------ - Update dependency versions - Add support for Python 3.10 2.0.19 ------ - Fix typo 2.0.18 ------ - Fixed issue with howdoi cache where cache misses would be printed to the console 2.0.17 ------ - New documentation and mkdocs - Fixed issue with how howdoi chooses the proper search engine (command line flags now override environment variables) - Added a search engine fallback if one of the search engines fails - Fixed issue with howdoi cache 2.0.16 ------ - Fix GDPR issue for those using howdoi in countries outside the US - Better support for using `HOWDOI_URL` 2.0.15 ------ - Add explainability with `-x` or `--explain` options - Better error checking for when search engines block queries - Using improved DuckDuckGo endpoint - Answer pages now fetched in parallel for speed improvement 2.0.14 ------ - Fix a number of bugs by switching from parsing Google links to looking for URLs instead 2.0.13 ------ - More permanent fix for extracting Google links 2.0.12 ------ - Hotfix for Google link formatting 2.0.11 ------ - Hotfix for Google link formatting 2.0.10 ------ - Hotfix for new Google classnames - Separate requirements.txt files for prod and dev 2.0.9 ------ - Cleaner command line options that also include environment variables - README updates 2.0.8 ------ - Fix issue for answers that have no code in the answer but code in the comments - Add range checks for -n and -p flags - Moved from Travis to Github Actions - Dropped Python 2.7 support 2.0.7 ------ - Update for new Google CSS style 2.0.6 ------ - Fix issue where `-a` would not return a proper response due to updated CSS on StackOverflow 2.0.5 ------ - New logo and colors! 2.0.4 ------ - Cachelib rollback to support Python 2.7 - Better error message when Google is being blocked (for example in China) 2.0.3 ------ - Bring back Python 2.7 support (for now) 2.0.2 ------ - Fixed keep support for stashing and viewing answers 2.0.1 ------ - Added JSON output with the -j flag (great for consuming howdoi results for use in other apps) - Added stashing ability for saving useful answer for later (based on https://github.com/OrkoHunter/keep) - Added caching for tests to prevent being rate limited by Google while developing - Added easier method for calling howdoi when imported (howdoi.howdoi) 1.2.1 ------ - Fix dependency issue 1.2.0 ------ - Massive speed improvements of startup, answer fetching, and caching - Command line flags for alternate search engines - Remove duplicate answers 1.1.14 ------ - Links displayed with markdown syntax - Improved performance and caching (again) 1.1.13 ------ - Improved performance and caching - More friendly answer display - Added support for Python 3.6 - Removed support for Python 2.6 1.1.12 ------ - Add additional search engine support 1.1.11 ------ - Fix issue with UTF-8 encoding 1.1.10 ------ - Include the link in output when asking for >1 answer - Compatibility with linuxbrew 1.1.9 ------ - Fix issue with upload to PyPI 1.1.8 ------ - Fix colorization when HOWDOI_COLORIZE env variable is enabled - Fix certificate validation when SSL disabled 1.1.7 ------ - Add Localization support with HOWDOI_LOCALIZATION env variable (Currently only pt-br and en) 1.1.6 ------ - Updates for Python3 - Updates for caching 1.1.5 ------ - Updates for Python3 - Fix issues with cache - Allow disabling SSL when accessing Google 1.1.4 ------ - Added caching 1.1.3 ------ - Added fix to handle change in Google search page HTML - Updated Travis CI tests 1.1.2 ------ - Compatibility fixes for Python3.2 - Travis CI tests now being run for Python 2.6, 2.7, 3.2, and 3.3 1.1.1 ------ - Added message when question has no answer 1.1 ------ - Added multiple answers with -n/--num-answers flag - Added colorized output with -c/--color flag - Added answer link to the bottom of questions with -a/--all flag - Unit tests now managed through Travis CI 1.0 ------ - Added support for Python3 - Switched to the requests library instead of urllib2 - Project status changed to Production/Stable - Added troubleshooting steps to the README 0.2 ------ - Added sane flags - Now using ``/usr/bin/env python`` instead of ``/usr/bin/python`` - Updated README for brew installation instructions 0.1.2 ------ - Added Windows executable - Updated README for pip installation instructions 0.1.1 ------ - Added to PyPI 0.1 ------ - We're doing it live! Keywords: howdoi help console command line answer Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Documentation Description-Content-Type: text/markdown ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664817974.0 howdoi-2.0.20/README.md0000644000076500000240000001633014316615466013603 0ustar00gleitzstaff

Sherlock, your neighborhood command-line sloth sleuth

howdoi

Instant coding answers via the command line

⚡ Never open your browser to look for help again ⚡

build status downloads Python versions

------------------------------------------------------------------------ ## Introduction to howdoi Are you a hack programmer? Do you find yourself constantly Googling for how to do basic programming tasks? Suppose you want to know how to format a date in bash. Why open your browser and read through blogs (risking major distraction) when you can simply stay in the console and ask howdoi: $ howdoi format date bash > DATE=`date +%Y-%m-%d` howdoi will answer all sorts of queries: $ howdoi print stack trace python > import traceback > > try: > 1/0 > except: > print '>>> traceback <<<' > traceback.print_exc() > print '>>> end of traceback <<<' > traceback.print_exc() $ howdoi convert mp4 to animated gif > video=/path/to/video.avi > outdir=/path/to/output.gif > mplayer "$video" \ > -ao null \ > -ss "00:01:00" \ # starting point > -endpos 10 \ # duration in second > -vo gif89a:fps=13:output=$outdir \ > -vf scale=240:180 $ howdoi create tar archive > tar -cf backup.tar --exclude "www/subf3" www [![image](http://imgs.xkcd.com/comics/tar.png)](https://xkcd.com/1168/) ## Installation pip install howdoi ## Usage ### New to howdoi? howdoi howdoi ### RTFM - [Introduction and installation](http://gleitz.github.io/howdoi/introduction/) - [Usage](http://gleitz.github.io/howdoi/usage/) - [Contributing to howdoi](http://gleitz.github.io/howdoi/contributing_to_howdoi/) - [Advanced usage](http://gleitz.github.io/howdoi/howdoi_advanced_usage/) - [Troubleshooting](http://gleitz.github.io/howdoi/troubleshooting/) ### Commands usage: howdoi [-h] [-p POS] [-n NUM] [-a] [-l] [-c] [-x] [-C] [-j] [-v] [-e [ENGINE]] [--save] [--view] [--remove] [--empty] [QUERY ...] instant coding answers via the command line positional arguments: QUERY the question to answer optional arguments: -h, --help show this help message and exit -p POS, --pos POS select answer in specified position (default: 1) -n NUM, --num NUM number of answers to return (default: 1) -a, --all display the full text of the answer -l, --link display only the answer link -c, --color enable colorized output -x, --explain explain how answer was chosen -C, --clear-cache clear the cache -j, --json return answers in raw json format -v, --version display the current version of howdoi -e [ENGINE], --engine [ENGINE] search engine for this query (google, bing, duckduckgo) --save, --stash stash a howdoi answer --view view your stash --remove remove an entry in your stash --empty empty your stash environment variable examples: HOWDOI_COLORIZE=1 HOWDOI_DISABLE_CACHE=1 HOWDOI_DISABLE_SSL=1 HOWDOI_SEARCH_ENGINE=google HOWDOI_URL=serverfault.com Using the howdoi stashing feature (for more advanced features view the [keep documentation](https://github.com/OrkoHunter/keep)). stashing: howdoi --save QUERY viewing: howdoi --view removing: howdoi --remove (will be prompted which answer to delete) emptying: howdoi --empty (empties entire stash, will be prompted to confirm) As a shortcut, if you commonly use the same parameters each time and don\'t want to type them, add something similar to your .bash_profile (or otherwise). This example gives you 5 colored results each time. alias h='function hdi(){ howdoi $* -c -n 5; }; hdi' And then to run it from the command line simply type: $ h format date bash You can also search other [StackExchange properties](https://stackexchange.com/sites#traffic) for answers: HOWDOI_URL=cooking.stackexchange.com howdoi make pesto or as an alias: alias hcook='function hcook(){ HOWDOI_URL=cooking.stackexchange.com howdoi $* ; }; hcook' hcook make pesto Other useful aliases: alias hless='function hdi(){ howdoi $* -c | less --raw-control-chars --quit-if-one-screen --no-init; }; hdi' ## Contributors - Benjamin Gleitzman ([\@gleitz](http://twitter.com/gleitz)) - Yanlam Ko ([\@YKo20010](https://github.com/YKo20010)) - Diana Arreola ([\@diarreola](https://github.com/diarreola)) - Eyitayo Ogunbiyi ([\@tayoogunbiyi](https://github.com/tayoogunbiyi)) - Chris Nguyen ([\@chrisngyn](https://github.com/chrisngyn)) - Shageldi Ovezov ([\@ovezovs](https://github.com/chrisngyn)) - Mwiza Simbeye ([\@mwizasimbeye11](https://github.com/mwizasimbeye11)) - Shantanu Verma ([\@SaurusXI](https://github.com/SaurusXI)) - Sheza Munir ([\@ShezaMunir](https://github.com/ShezaMunir)) - Jyoti Bisht ([\@joeyouss](https://github.com/joeyouss)) - And [more!](https://github.com/gleitz/howdoi/graphs/contributors) ## How to contribute We welcome contributions that make howdoi better and improve the existing functionalities of the project. We have created a separate [guide to contributing to howdoi](http://gleitz.github.io/howdoi/contributing_to_howdoi/) that explains how to get up and running with your first pull request. ## Notes - Works with Python 3.5 and newer. Unfortunately Python 2.7 support has been discontinued :( - There is a [GUI that wraps howdoi](https://pypi.org/project/pysimplegui-howdoi/) - There is a [Flask webapp that wraps howdoi](https://howdoi.maxbridgland.com) - An [Alfred Workflow](http://blog.gleitzman.com/post/48539944559/howdoi-alfred-even-more-instant-answers) for howdoi - Slack integration available through [slack-howdoi](https://github.com/ellisonleao/slack-howdoi) - Telegram integration available through [howdoi-telegram](https://github.com/aahnik/howdoi-telegram) - Special thanks to Rich Jones ([\@miserlou](https://github.com/miserlou)) for the idea - More thanks to [Ben Bronstein](https://benbronstein.com/) for the logo ## Visual Studio Code Extension Installation Head over to the [MarketPlace](https://marketplace.visualstudio.com/items?itemName=howdoi-org.howdoi) to install the extension. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1627661451.0 howdoi-2.0.20/fastentrypoints.py0000644000076500000240000000770714101022213016130 0ustar00gleitzstaff# flake8: noqa # pylint: skip-file # Copyright (c) 2016, Aaron Christianson # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' Monkey patch setuptools to write faster console_scripts with this format: import sys from mymodule import entry_function sys.exit(entry_function()) This is better. (c) 2016, Aaron Christianson http://github.com/ninjaaron/fast-entry_points ''' from setuptools.command import easy_install import re TEMPLATE = r''' # -*- coding: utf-8 -*- # EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}' __requires__ = '{3}' import re import sys from {0} import {1} if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit({2}())'''.lstrip() @classmethod def get_args(cls, dist, header=None): # noqa: D205,D400 """ Yield write_script() argument tuples for a distribution's console_scripts and gui_scripts entry points. """ if header is None: # pylint: disable=E1101 header = cls.get_header() spec = str(dist.as_requirement()) for type_ in 'console', 'gui': group = type_ + '_scripts' for name, ep in dist.get_entry_map(group).items(): # ensure_safe_name if re.search(r'[\\/]', name): raise ValueError("Path separators not allowed in script names") script_text = TEMPLATE.format( ep.module_name, ep.attrs[0], '.'.join(ep.attrs), spec, group, name) # pylint: disable=E1101 args = cls._get_script_args(type_, name, header, script_text) for res in args: yield res # pylint: disable=E1101 easy_install.ScriptWriter.get_args = get_args def main(): import os import re import shutil import sys dests = sys.argv[1:] or ['.'] filename = re.sub(r'\.pyc$', '.py', __file__) for dst in dests: shutil.copy(filename, dst) manifest_path = os.path.join(dst, 'MANIFEST.in') setup_path = os.path.join(dst, 'setup.py') # Insert the include statement to MANIFEST.in if not present with open(manifest_path, 'a+') as manifest: manifest.seek(0) manifest_content = manifest.read() if 'include fastentrypoints.py' not in manifest_content: manifest.write(('\n' if manifest_content else '') + 'include fastentrypoints.py') # Insert the import statement to setup.py if not present with open(setup_path, 'a+') as setup: setup.seek(0) setup_content = setup.read() if 'import fastentrypoints' not in setup_content: setup.seek(0) setup.truncate() setup.write('import fastentrypoints\n' + setup_content) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1664818366.102615 howdoi-2.0.20/howdoi/0000755000076500000240000000000014316616276013612 5ustar00gleitzstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818280.0 howdoi-2.0.20/howdoi/__init__.py0000644000076500000240000000002714316616150015711 0ustar00gleitzstaff__version__ = '2.0.20' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1634571421.0 howdoi-2.0.20/howdoi/__main__.py0000644000076500000240000000007714133312235015671 0ustar00gleitzstafffrom .howdoi import command_line_runner command_line_runner() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1627661451.0 howdoi-2.0.20/howdoi/errors.py0000644000076500000240000000022214101022213015442 0ustar00gleitzstaffclass GoogleValidationError(Exception): pass class BingValidationError(Exception): pass class DDGValidationError(Exception): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664776925.0 howdoi-2.0.20/howdoi/howdoi.py0000644000076500000240000006611614316475335015466 0ustar00gleitzstaff#!/usr/bin/env python ###################################################### # # howdoi - instant coding answers via the command line # written by Benjamin Gleitzman (gleitz@mit.edu) # inspired by Rich Jones (rich@anomos.info) # ###################################################### import gc gc.disable() import argparse import inspect import json import os import re import sys import textwrap from urllib.request import getproxies from urllib.parse import quote as url_quote, urlparse, parse_qs from multiprocessing import Pool import logging import appdirs import requests from cachelib import FileSystemCache, NullCache from keep import utils as keep_utils from pygments.lexers import guess_lexer, get_lexer_by_name from pygments.util import ClassNotFound from rich.syntax import Syntax from rich.console import Console from pyquery import PyQuery as pq from requests.exceptions import ConnectionError as RequestsConnectionError from requests.exceptions import SSLError from colorama import init init() from howdoi import __version__ from howdoi.errors import GoogleValidationError, BingValidationError, DDGValidationError logging.basicConfig(format='%(levelname)s: %(message)s') if os.getenv('HOWDOI_DISABLE_SSL'): # Set http instead of https SCHEME = 'http://' VERIFY_SSL_CERTIFICATE = False else: SCHEME = 'https://' VERIFY_SSL_CERTIFICATE = True SUPPORTED_SEARCH_ENGINES = ('google', 'bing', 'duckduckgo') URL = os.getenv('HOWDOI_URL') or 'stackoverflow.com' USER_AGENTS = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100 101 Firefox/22.0', 'Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko/20100101 Firefox/11.0', ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.5 (KHTML, like Gecko) ' 'Chrome/19.0.1084.46 Safari/536.5'), ('Mozilla/5.0 (Windows; Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46' 'Safari/536.5'),) SEARCH_URLS = { 'bing': SCHEME + 'www.bing.com/search?q=site:{0}%20{1}&hl=en', 'google': SCHEME + 'www.google.com/search?q=site:{0}%20{1}&hl=en', 'duckduckgo': SCHEME + 'duckduckgo.com/html?q=site:{0}%20{1}&t=hj&ia=web' } BLOCK_INDICATORS = ( 'form id="captcha-form"', 'This page appears when Google automatically detects requests coming from your computer ' 'network which appear to be in violation of the Terms of Service' ) BLOCKED_QUESTION_FRAGMENTS = ( 'webcache.googleusercontent.com', ) STAR_HEADER = '\u2605' ANSWER_HEADER = '{2} Answer from {0} {2}\n{1}' NO_ANSWER_MSG = '< no answer given >' CACHE_EMPTY_VAL = "NULL" CACHE_DIR = appdirs.user_cache_dir('howdoi') CACHE_ENTRY_MAX = 128 HTML_CACHE_PATH = 'page_cache' SUPPORTED_HELP_QUERIES = ['use howdoi', 'howdoi', 'run howdoi', 'setup howdoi', 'do howdoi', 'howdoi howdoi', 'howdoi use howdoi'] NO_RESULTS_MESSAGE = "Sorry, couldn't find any help with that topic" # variables for text formatting, prepend to string to begin text formatting. BOLD = '\033[1m' GREEN = '\033[92m' RED = '\033[91m' UNDERLINE = '\033[4m' END_FORMAT = '\033[0m' # append to string to end text formatting. # stash options STASH_SAVE = 'save' STASH_VIEW = 'view' STASH_REMOVE = 'remove' STASH_EMPTY = 'empty' BLOCKED_ENGINES = [] if os.getenv('HOWDOI_DISABLE_CACHE'): # works like an always empty cache cache = NullCache() else: cache = FileSystemCache(CACHE_DIR, CACHE_ENTRY_MAX, default_timeout=0) howdoi_session = requests.session() class BlockError(RuntimeError): pass class IntRange: def __init__(self, imin=None, imax=None): self.imin = imin self.imax = imax def __call__(self, arg): try: value = int(arg) except ValueError as value_error: raise self.exception() from value_error if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax): raise self.exception() return value def exception(self): if self.imin is not None and self.imax is not None: return argparse.ArgumentTypeError(f'Must be an integer in the range [{self.imin}, {self.imax}]') if self.imin is not None: return argparse.ArgumentTypeError(f'Must be an integer >= {self.imin}') if self.imax is not None: return argparse.ArgumentTypeError(f'Must be an integer <= {self.imax}') return argparse.ArgumentTypeError('Must be an integer') def _random_int(width): bres = os.urandom(width) if sys.version < '3': ires = int(bres.encode('hex'), 16) else: ires = int.from_bytes(bres, 'little') return ires def _random_choice(seq): return seq[_random_int(1) % len(seq)] def get_proxies(): proxies = getproxies() filtered_proxies = {} for key, value in proxies.items(): if key.startswith('http'): if not value.startswith('http'): filtered_proxies[key] = f'http://{value}' else: filtered_proxies[key] = value return filtered_proxies def _get_result(url): try: resp = howdoi_session.get(url, headers={'User-Agent': _random_choice(USER_AGENTS)}, proxies=get_proxies(), verify=VERIFY_SSL_CERTIFICATE, cookies={'CONSENT': 'YES+US.en+20170717-00-0'}) resp.raise_for_status() return resp.text except requests.exceptions.SSLError as error: logging.error('%sEncountered an SSL Error. Try using HTTP instead of ' 'HTTPS by setting the environment variable "HOWDOI_DISABLE_SSL".\n%s', RED, END_FORMAT) raise error def _get_from_cache(cache_key): # As of cachelib 0.3.0, it internally logging a warning on cache miss current_log_level = logging.getLogger().getEffectiveLevel() # Reduce the log level so the warning is not printed logging.getLogger().setLevel(logging.ERROR) page = cache.get(cache_key) # pylint: disable=assignment-from-none # Restore the log level logging.getLogger().setLevel(current_log_level) return page def _add_links_to_text(element): hyperlinks = element.find('a') for hyperlink in hyperlinks: pquery_object = pq(hyperlink) href = hyperlink.attrib['href'] copy = pquery_object.text() if copy == href: replacement = copy else: replacement = f'[{copy}]({href})' pquery_object.replace_with(replacement) def get_text(element): ''' return inner text in pyquery element ''' _add_links_to_text(element) try: return element.text(squash_space=False) except TypeError: return element.text() def _extract_links_from_bing(html): html.remove_namespaces() return [a.attrib['href'] for a in html('.b_algo')('h2')('a')] def _clean_google_link(link): if '/url?' in link: parsed_link = urlparse(link) query_params = parse_qs(parsed_link.query) url_params = query_params.get('q', []) or query_params.get('url', []) if url_params: return url_params[0] return link def _extract_links_from_google(query_object): html = query_object.html() link_pattern = re.compile(fr"https?://{URL}/questions/[0-9]*/[a-z0-9-]*") links = link_pattern.findall(html) links = [_clean_google_link(link) for link in links] return links def _extract_links_from_duckduckgo(html): html.remove_namespaces() links_anchors = html.find('a.result__a') results = [] for anchor in links_anchors: link = anchor.attrib['href'] url_obj = urlparse(link) parsed_url = parse_qs(url_obj.query).get('uddg', '') if parsed_url: results.append(parsed_url[0]) return results def _extract_links(html, search_engine): if search_engine == 'bing': return _extract_links_from_bing(html) if search_engine == 'duckduckgo': return _extract_links_from_duckduckgo(html) return _extract_links_from_google(html) def _get_search_url(search_engine): return SEARCH_URLS.get(search_engine, SEARCH_URLS['google']) def _is_blocked(page): for indicator in BLOCK_INDICATORS: if page.find(indicator) != -1: return True return False def _get_links(query): search_engine = os.getenv('HOWDOI_SEARCH_ENGINE', 'google') search_url = _get_search_url(search_engine).format(URL, url_quote(query)) logging.info('Searching %s with URL: %s', search_engine, search_url) try: result = _get_result(search_url) except requests.HTTPError: logging.info('Received HTTPError') result = None if not result or _is_blocked(result): logging.error('%sUnable to find an answer because the search engine temporarily blocked the request. ' 'Attempting to use a different search engine.%s', RED, END_FORMAT) raise BlockError('Temporary block by search engine') html = pq(result) links = _extract_links(html, search_engine) if len(links) == 0: logging.info('Search engine %s found no StackOverflow links, returned HTML is:', search_engine) logging.info(result) return list(dict.fromkeys(links)) # remove any duplicates def get_link_at_pos(links, position): if not links: return False if len(links) >= position: link = links[position - 1] else: link = links[-1] return link def _format_output(args, code): if not args['color']: return code lexer = None # try to find a lexer using the StackOverflow tags # or the query arguments for keyword in args['query'].split() + args['tags']: try: lexer = get_lexer_by_name(keyword).name break except ClassNotFound: pass # no lexer found above, use the guesser if not lexer: try: lexer = guess_lexer(code).name except ClassNotFound: return code syntax = Syntax(code, lexer, background_color="default", line_numbers=False) console = Console(record=True) with console.capture() as capture: console.print(syntax) return capture.get() def _is_question(link): for fragment in BLOCKED_QUESTION_FRAGMENTS: if fragment in link: return False return re.search(r'questions/\d+/', link) def _get_questions(links): return [link for link in links if _is_question(link)] def _get_answer(args, link): # pylint: disable=too-many-branches cache_key = _get_cache_key(link) page = _get_from_cache(cache_key) if not page: logging.info('Fetching page: %s', link) page = _get_result(link + '?answertab=votes') cache.set(cache_key, page) else: logging.info('Using cached page: %s', link) html = pq(page) first_answer = html('.answercell').eq(0) or html('.answer').eq(0) instructions = first_answer.find('pre') or first_answer.find('code') args['tags'] = [t.text for t in html('.post-tag')] # make decision on answer body class. if first_answer.find(".js-post-body"): answer_body_cls = ".js-post-body" else: # rollback to post-text class answer_body_cls = ".post-text" if not instructions and not args['all']: logging.info('No code sample found, returning entire answer') text = get_text(first_answer.find(answer_body_cls).eq(0)) elif args['all']: logging.info('Returning entire answer') texts = [] for html_tag in first_answer.items(f'{answer_body_cls} > *'): current_text = get_text(html_tag) if current_text: if html_tag[0].tag in ['pre', 'code']: texts.append(_format_output(args, current_text)) else: texts.append(current_text) text = '\n'.join(texts) else: text = _format_output(args, get_text(instructions.eq(0))) if text is None: logging.info('%sAnswer was empty%s', RED, END_FORMAT) text = NO_ANSWER_MSG text = text.strip() return text def _get_links_with_cache(query): cache_key = _get_cache_key(query) res = _get_from_cache(cache_key) if res: logging.info('Using cached links') if res == CACHE_EMPTY_VAL: logging.info('No StackOverflow links found in cached search engine results - will make live query') else: return res links = _get_links(query) if not links: cache.set(cache_key, CACHE_EMPTY_VAL) question_links = _get_questions(links) cache.set(cache_key, question_links or CACHE_EMPTY_VAL) return question_links def build_splitter(splitter_character='=', splitter_length=80): return '\n' + splitter_character * splitter_length + '\n\n' def _get_answers(args): """ @args: command-line arguments returns: array of answers and their respective metadata False if unable to get answers """ question_links = _get_links_with_cache(args['query']) if not question_links: return False initial_pos = args['pos'] - 1 final_pos = initial_pos + args['num_answers'] question_links = question_links[initial_pos:final_pos] search_engine = os.getenv('HOWDOI_SEARCH_ENGINE', 'google') logging.info('Links from %s found on %s: %s', URL, search_engine, len(question_links)) logging.info('URL: %s', '\n '.join(question_links)) logging.info('Answers requested: %s, Starting at position: %s', args["num_answers"], args['pos']) with Pool() as pool: answers = pool.starmap( _get_answer_worker, [(args, link) for link in question_links] ) answers = [a for a in answers if a.get('answer')] for i, answer in enumerate(answers, 1): answer['position'] = i logging.info('Total answers returned: %s', len(answers)) return answers or False def _get_answer_worker(args, link): answer = _get_answer(args, link) result = { 'answer': None, 'link': None, 'position': None } multiple_answers = (args['num_answers'] > 1 or args['all']) if not answer: return result if not args['link'] and not args['json_output'] and multiple_answers: answer = ANSWER_HEADER.format(link, answer, STAR_HEADER) answer += '\n' result['answer'] = answer result['link'] = link return result def _clear_cache(): global cache # pylint: disable=global-statement,invalid-name if not cache: cache = FileSystemCache(CACHE_DIR, CACHE_ENTRY_MAX, 0) return cache.clear() def _is_help_query(query): return any(query.lower() == help_query for help_query in SUPPORTED_HELP_QUERIES) def _format_answers(args, res): if "error" in res: return f'ERROR: {RED}{res["error"]}{END_FORMAT}' if args["json_output"]: return json.dumps(res) formatted_answers = [] for answer in res: next_ans = answer["answer"] if args["link"]: # if we only want links next_ans = answer["link"] formatted_answers.append(next_ans or NO_RESULTS_MESSAGE) return build_splitter().join(formatted_answers) def _get_help_instructions(): instruction_splitter = build_splitter(' ', 60) query = 'print hello world in python' instructions = [ 'Here are a few popular howdoi commands ', '>>> howdoi {} (default query)', '>>> howdoi {} -a (read entire answer)', '>>> howdoi {} -n [number] (retrieve n number of answers)', '>>> howdoi {} -l (display only a link to where the answer is from', '>>> howdoi {} -c (Add colors to the output)', '>>> howdoi {} -e (Specify the search engine you want to use e.g google,bing)' ] instructions = map(lambda s: s.format(query), instructions) return instruction_splitter.join(instructions) def _get_cache_key(args): frame = inspect.currentframe() calling_func = inspect.getouterframes(frame)[1].function return calling_func + str(args) + __version__ def format_stash_item(fields, index=-1): title = fields['alias'] description = fields['desc'] item_num = index + 1 if index == -1: return f'{UNDERLINE}{BOLD}$ {title}{END_FORMAT}\n\n{description}\n' return f'{UNDERLINE}{BOLD}$ [{item_num}] {title}{END_FORMAT}\n\n{description}\n' def print_stash(stash_list=None): if not stash_list or len(stash_list) == 0: stash_list = ['\nSTASH LIST:'] commands = keep_utils.read_commands() if commands is None or len(commands.items()) == 0: logging.error('%sNo commands found in stash. ' 'Add a command with "howdoi --%s ".%s', RED, STASH_SAVE, END_FORMAT) return for _, fields in commands.items(): stash_list.append(format_stash_item(fields)) else: stash_list = [format_stash_item(x['fields'], i) for i, x in enumerate(stash_list)] print(build_splitter('#').join(stash_list)) def _get_stash_key(args): stash_args = {} ignore_keys = [STASH_SAVE, STASH_VIEW, STASH_REMOVE, STASH_EMPTY, 'tags'] # ignore these for stash key for key in args: if key not in ignore_keys: stash_args[key] = args[key] return str(stash_args) def _stash_remove(cmd_key, title): commands = keep_utils.read_commands() if commands is not None and cmd_key in commands: keep_utils.remove_command(cmd_key) print(f'\n{BOLD}{GREEN}"{title}" removed from stash{END_FORMAT}\n') else: print(f'\n{BOLD}{RED}"{title}" not found in stash{END_FORMAT}\n') def _stash_save(cmd_key, title, answer): try: keep_utils.save_command(cmd_key, answer, title) except FileNotFoundError: os.system('keep init') keep_utils.save_command(cmd_key, answer, title) finally: print_stash() def _parse_cmd(args, res): answer = _format_answers(args, res) cmd_key = _get_stash_key(args) title = ''.join(args['query']) if args[STASH_SAVE]: _stash_save(cmd_key, title, answer) return '' if args[STASH_REMOVE]: _stash_remove(cmd_key, title) return '' return answer def howdoi(raw_query): if isinstance(raw_query, str): # you can pass either a raw or a parsed query parser = get_parser() args = vars(parser.parse_args(raw_query.split(' '))) else: args = raw_query search_engine = args['search_engine'] or os.getenv('HOWDOI_SEARCH_ENGINE') or 'google' os.environ['HOWDOI_SEARCH_ENGINE'] = search_engine if search_engine not in SUPPORTED_SEARCH_ENGINES: supported_search_engines = ', '.join(SUPPORTED_SEARCH_ENGINES) message = f'Unsupported engine {search_engine}. The supported engines are: {supported_search_engines}' res = {'error': message} return _parse_cmd(args, res) args['query'] = ' '.join(args['query']).replace('?', '') cache_key = _get_cache_key(args) if _is_help_query(args['query']): return _get_help_instructions() + '\n' res = _get_from_cache(cache_key) if res: logging.info('Using cached response (add -C to clear the cache)') return _parse_cmd(args, res) logging.info('Fetching answers for query: %s', args["query"]) try: res = _get_answers(args) if not res: message = NO_RESULTS_MESSAGE if not args['explain']: message = f'{message} (use --explain to learn why)' res = {'error': message} cache.set(cache_key, res) except (RequestsConnectionError, SSLError): res = {'error': f'Unable to reach {search_engine}. Do you need to use a proxy?\n'} except BlockError: BLOCKED_ENGINES.append(search_engine) next_engine = next((engine for engine in SUPPORTED_SEARCH_ENGINES if engine not in BLOCKED_ENGINES), None) if next_engine is None: res = {'error': 'Unable to get a response from any search engine\n'} else: args['search_engine'] = next_engine args['query'] = args['query'].split() logging.info('%sRetrying search with %s%s', GREEN, next_engine, END_FORMAT) return howdoi(args) return _parse_cmd(args, res) def get_parser(): parser = argparse.ArgumentParser(description='instant coding answers via the command line', epilog=textwrap.dedent('''\ environment variable examples: HOWDOI_COLORIZE=1 HOWDOI_DISABLE_CACHE=1 HOWDOI_DISABLE_SSL=1 HOWDOI_SEARCH_ENGINE=google HOWDOI_URL=serverfault.com '''), formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('query', metavar='QUERY', type=str, nargs='*', help='the question to answer') parser.add_argument('-p', '--pos', help='select answer in specified position (default: 1)', default=1, type=IntRange(1, 20), metavar='POS') parser.add_argument('-n', '--num', help='number of answers to return (default: 1)', dest='num_answers', default=1, type=IntRange(1, 20), metavar='NUM') parser.add_argument('--num-answers', help=argparse.SUPPRESS) parser.add_argument('-a', '--all', help='display the full text of the answer', action='store_true') parser.add_argument('-l', '--link', help='display only the answer link', action='store_true') parser.add_argument('-c', '--color', help='enable colorized output', action='store_true') parser.add_argument('-x', '--explain', help='explain how answer was chosen', action='store_true') parser.add_argument('-C', '--clear-cache', help='clear the cache', action='store_true') parser.add_argument('-j', '--json', help='return answers in raw json format', dest='json_output', action='store_true') parser.add_argument('--json-output', action='store_true', help=argparse.SUPPRESS) parser.add_argument('-v', '--version', help='display the current version of howdoi', action='store_true') parser.add_argument('-e', '--engine', help='search engine for this query (google, bing, duckduckgo)', dest='search_engine', nargs="?", metavar='ENGINE') parser.add_argument('--save', '--stash', help='stash a howdoi answer', action='store_true') parser.add_argument('--view', help='view your stash', action='store_true') parser.add_argument('--remove', help='remove an entry in your stash', action='store_true') parser.add_argument('--empty', help='empty your stash', action='store_true') parser.add_argument('--sanity-check', help=argparse.SUPPRESS, action='store_true') return parser def _sanity_check(engine, test_query=None): parser = get_parser() if not test_query: test_query = 'format date bash' args = vars(parser.parse_args(test_query.split())) args['search_engine'] = engine try: result = howdoi(args) # Perhaps better to use `-j` and then check for an error message # rather than trying to enumerate all the error strings assert "Sorry" not in result and "Unable to" not in result except AssertionError as exc: if engine == 'google': raise GoogleValidationError from exc if engine == 'bing': raise BingValidationError from exc raise DDGValidationError from exc def prompt_stash_remove(args, stash_list, view_stash=True): if view_stash: print_stash(stash_list) last_index = len(stash_list) prompt = f'{BOLD}> Select a stash command to remove [1-{last_index}] (0 to cancel): {END_FORMAT}' user_input = input(prompt) try: user_input = int(user_input) if user_input == 0: return if user_input < 1 or user_input > last_index: logging.error('\n%sInput index is invalid.%s', RED, END_FORMAT) prompt_stash_remove(args, stash_list, False) return cmd = stash_list[user_input - 1] cmd_key = cmd['command'] cmd_name = cmd['fields']['alias'] _stash_remove(cmd_key, cmd_name) return except ValueError: logging.error('\n%sInvalid input. Must specify index of command.%s', RED, END_FORMAT) prompt_stash_remove(args, stash_list, False) return def perform_sanity_check(): '''Perform sanity check. Returns exit code for program. An exit code of -1 means a validation error was encountered. ''' global cache # pylint: disable=global-statement,invalid-name # Disable cache to avoid cached answers while performing the checks cache = NullCache() exit_code = 0 for engine in ['google']: # 'bing' and 'duckduckgo' throw various block errors print(f'Checking {engine}...') try: _sanity_check(engine) except (GoogleValidationError, BingValidationError, DDGValidationError): logging.error('%s%s query failed%s', RED, engine, END_FORMAT) exit_code = -1 if exit_code == 0: print(f'{GREEN}Ok{END_FORMAT}') return exit_code def command_line_runner(): # pylint: disable=too-many-return-statements,too-many-branches parser = get_parser() args = vars(parser.parse_args()) if args['version']: print(__version__) return if args['explain']: logging.getLogger().setLevel(logging.INFO) logging.info('Version: %s', __version__) if args['sanity_check']: sys.exit( perform_sanity_check() ) if args['clear_cache']: if _clear_cache(): print(f'{GREEN}Cache cleared successfully{END_FORMAT}') else: logging.error('%sClearing cache failed%s', RED, END_FORMAT) if args[STASH_VIEW]: print_stash() return if args[STASH_EMPTY]: os.system('keep init') return if args[STASH_REMOVE] and len(args['query']) == 0: commands = keep_utils.read_commands() if commands is None or len(commands.items()) == 0: logging.error('%sNo commands found in stash. ' 'Add a command with "howdoi --%s ".%s', RED, STASH_SAVE, END_FORMAT) return stash_list = [{'command': cmd, 'fields': field} for cmd, field in commands.items()] prompt_stash_remove(args, stash_list) return if not args['query']: parser.print_help() return if os.getenv('HOWDOI_COLORIZE'): args['color'] = True howdoi_result = howdoi(args) if os.name == 'nt': # Windows print(howdoi_result) else: utf8_result = howdoi_result.encode('utf-8', 'ignore') # Write UTF-8 to stdout: https://stackoverflow.com/a/3603160 sys.stdout.buffer.write(utf8_result) # close the session to release connection howdoi_session.close() if __name__ == '__main__': command_line_runner() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1664818366.1095142 howdoi-2.0.20/howdoi.egg-info/0000755000076500000240000000000014316616276015304 5ustar00gleitzstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/PKG-INFO0000644000076500000240000003715714316616275016415 0ustar00gleitzstaffMetadata-Version: 2.1 Name: howdoi Version: 2.0.20 Summary: Instant coding answers via the command line Home-page: https://github.com/gleitz/howdoi Author: Benjamin Gleitzman Author-email: gleitz@mit.edu Maintainer: Benjamin Gleitzman Maintainer-email: gleitz@mit.edu License: MIT Description:

Sherlock, your neighborhood command-line sloth sleuth

howdoi

Instant coding answers via the command line

⚡ Never open your browser to look for help again ⚡

build status downloads Python versions

------------------------------------------------------------------------ ## Introduction to howdoi Are you a hack programmer? Do you find yourself constantly Googling for how to do basic programming tasks? Suppose you want to know how to format a date in bash. Why open your browser and read through blogs (risking major distraction) when you can simply stay in the console and ask howdoi: $ howdoi format date bash > DATE=`date +%Y-%m-%d` howdoi will answer all sorts of queries: $ howdoi print stack trace python > import traceback > > try: > 1/0 > except: > print '>>> traceback <<<' > traceback.print_exc() > print '>>> end of traceback <<<' > traceback.print_exc() $ howdoi convert mp4 to animated gif > video=/path/to/video.avi > outdir=/path/to/output.gif > mplayer "$video" \ > -ao null \ > -ss "00:01:00" \ # starting point > -endpos 10 \ # duration in second > -vo gif89a:fps=13:output=$outdir \ > -vf scale=240:180 $ howdoi create tar archive > tar -cf backup.tar --exclude "www/subf3" www [![image](http://imgs.xkcd.com/comics/tar.png)](https://xkcd.com/1168/) ## Installation pip install howdoi ## Usage ### New to howdoi? howdoi howdoi ### RTFM - [Introduction and installation](http://gleitz.github.io/howdoi/introduction/) - [Usage](http://gleitz.github.io/howdoi/usage/) - [Contributing to howdoi](http://gleitz.github.io/howdoi/contributing_to_howdoi/) - [Advanced usage](http://gleitz.github.io/howdoi/howdoi_advanced_usage/) - [Troubleshooting](http://gleitz.github.io/howdoi/troubleshooting/) ### Commands usage: howdoi [-h] [-p POS] [-n NUM] [-a] [-l] [-c] [-x] [-C] [-j] [-v] [-e [ENGINE]] [--save] [--view] [--remove] [--empty] [QUERY ...] instant coding answers via the command line positional arguments: QUERY the question to answer optional arguments: -h, --help show this help message and exit -p POS, --pos POS select answer in specified position (default: 1) -n NUM, --num NUM number of answers to return (default: 1) -a, --all display the full text of the answer -l, --link display only the answer link -c, --color enable colorized output -x, --explain explain how answer was chosen -C, --clear-cache clear the cache -j, --json return answers in raw json format -v, --version display the current version of howdoi -e [ENGINE], --engine [ENGINE] search engine for this query (google, bing, duckduckgo) --save, --stash stash a howdoi answer --view view your stash --remove remove an entry in your stash --empty empty your stash environment variable examples: HOWDOI_COLORIZE=1 HOWDOI_DISABLE_CACHE=1 HOWDOI_DISABLE_SSL=1 HOWDOI_SEARCH_ENGINE=google HOWDOI_URL=serverfault.com Using the howdoi stashing feature (for more advanced features view the [keep documentation](https://github.com/OrkoHunter/keep)). stashing: howdoi --save QUERY viewing: howdoi --view removing: howdoi --remove (will be prompted which answer to delete) emptying: howdoi --empty (empties entire stash, will be prompted to confirm) As a shortcut, if you commonly use the same parameters each time and don\'t want to type them, add something similar to your .bash_profile (or otherwise). This example gives you 5 colored results each time. alias h='function hdi(){ howdoi $* -c -n 5; }; hdi' And then to run it from the command line simply type: $ h format date bash You can also search other [StackExchange properties](https://stackexchange.com/sites#traffic) for answers: HOWDOI_URL=cooking.stackexchange.com howdoi make pesto or as an alias: alias hcook='function hcook(){ HOWDOI_URL=cooking.stackexchange.com howdoi $* ; }; hcook' hcook make pesto Other useful aliases: alias hless='function hdi(){ howdoi $* -c | less --raw-control-chars --quit-if-one-screen --no-init; }; hdi' ## Contributors - Benjamin Gleitzman ([\@gleitz](http://twitter.com/gleitz)) - Yanlam Ko ([\@YKo20010](https://github.com/YKo20010)) - Diana Arreola ([\@diarreola](https://github.com/diarreola)) - Eyitayo Ogunbiyi ([\@tayoogunbiyi](https://github.com/tayoogunbiyi)) - Chris Nguyen ([\@chrisngyn](https://github.com/chrisngyn)) - Shageldi Ovezov ([\@ovezovs](https://github.com/chrisngyn)) - Mwiza Simbeye ([\@mwizasimbeye11](https://github.com/mwizasimbeye11)) - Shantanu Verma ([\@SaurusXI](https://github.com/SaurusXI)) - Sheza Munir ([\@ShezaMunir](https://github.com/ShezaMunir)) - Jyoti Bisht ([\@joeyouss](https://github.com/joeyouss)) - And [more!](https://github.com/gleitz/howdoi/graphs/contributors) ## How to contribute We welcome contributions that make howdoi better and improve the existing functionalities of the project. We have created a separate [guide to contributing to howdoi](http://gleitz.github.io/howdoi/contributing_to_howdoi/) that explains how to get up and running with your first pull request. ## Notes - Works with Python 3.5 and newer. Unfortunately Python 2.7 support has been discontinued :( - There is a [GUI that wraps howdoi](https://pypi.org/project/pysimplegui-howdoi/) - There is a [Flask webapp that wraps howdoi](https://howdoi.maxbridgland.com) - An [Alfred Workflow](http://blog.gleitzman.com/post/48539944559/howdoi-alfred-even-more-instant-answers) for howdoi - Slack integration available through [slack-howdoi](https://github.com/ellisonleao/slack-howdoi) - Telegram integration available through [howdoi-telegram](https://github.com/aahnik/howdoi-telegram) - Special thanks to Rich Jones ([\@miserlou](https://github.com/miserlou)) for the idea - More thanks to [Ben Bronstein](https://benbronstein.com/) for the logo ## Visual Studio Code Extension Installation Head over to the [MarketPlace](https://marketplace.visualstudio.com/items?itemName=howdoi-org.howdoi) to install the extension. # News 2.0.20 ------ - Update dependency versions - Add support for Python 3.10 2.0.19 ------ - Fix typo 2.0.18 ------ - Fixed issue with howdoi cache where cache misses would be printed to the console 2.0.17 ------ - New documentation and mkdocs - Fixed issue with how howdoi chooses the proper search engine (command line flags now override environment variables) - Added a search engine fallback if one of the search engines fails - Fixed issue with howdoi cache 2.0.16 ------ - Fix GDPR issue for those using howdoi in countries outside the US - Better support for using `HOWDOI_URL` 2.0.15 ------ - Add explainability with `-x` or `--explain` options - Better error checking for when search engines block queries - Using improved DuckDuckGo endpoint - Answer pages now fetched in parallel for speed improvement 2.0.14 ------ - Fix a number of bugs by switching from parsing Google links to looking for URLs instead 2.0.13 ------ - More permanent fix for extracting Google links 2.0.12 ------ - Hotfix for Google link formatting 2.0.11 ------ - Hotfix for Google link formatting 2.0.10 ------ - Hotfix for new Google classnames - Separate requirements.txt files for prod and dev 2.0.9 ------ - Cleaner command line options that also include environment variables - README updates 2.0.8 ------ - Fix issue for answers that have no code in the answer but code in the comments - Add range checks for -n and -p flags - Moved from Travis to Github Actions - Dropped Python 2.7 support 2.0.7 ------ - Update for new Google CSS style 2.0.6 ------ - Fix issue where `-a` would not return a proper response due to updated CSS on StackOverflow 2.0.5 ------ - New logo and colors! 2.0.4 ------ - Cachelib rollback to support Python 2.7 - Better error message when Google is being blocked (for example in China) 2.0.3 ------ - Bring back Python 2.7 support (for now) 2.0.2 ------ - Fixed keep support for stashing and viewing answers 2.0.1 ------ - Added JSON output with the -j flag (great for consuming howdoi results for use in other apps) - Added stashing ability for saving useful answer for later (based on https://github.com/OrkoHunter/keep) - Added caching for tests to prevent being rate limited by Google while developing - Added easier method for calling howdoi when imported (howdoi.howdoi) 1.2.1 ------ - Fix dependency issue 1.2.0 ------ - Massive speed improvements of startup, answer fetching, and caching - Command line flags for alternate search engines - Remove duplicate answers 1.1.14 ------ - Links displayed with markdown syntax - Improved performance and caching (again) 1.1.13 ------ - Improved performance and caching - More friendly answer display - Added support for Python 3.6 - Removed support for Python 2.6 1.1.12 ------ - Add additional search engine support 1.1.11 ------ - Fix issue with UTF-8 encoding 1.1.10 ------ - Include the link in output when asking for >1 answer - Compatibility with linuxbrew 1.1.9 ------ - Fix issue with upload to PyPI 1.1.8 ------ - Fix colorization when HOWDOI_COLORIZE env variable is enabled - Fix certificate validation when SSL disabled 1.1.7 ------ - Add Localization support with HOWDOI_LOCALIZATION env variable (Currently only pt-br and en) 1.1.6 ------ - Updates for Python3 - Updates for caching 1.1.5 ------ - Updates for Python3 - Fix issues with cache - Allow disabling SSL when accessing Google 1.1.4 ------ - Added caching 1.1.3 ------ - Added fix to handle change in Google search page HTML - Updated Travis CI tests 1.1.2 ------ - Compatibility fixes for Python3.2 - Travis CI tests now being run for Python 2.6, 2.7, 3.2, and 3.3 1.1.1 ------ - Added message when question has no answer 1.1 ------ - Added multiple answers with -n/--num-answers flag - Added colorized output with -c/--color flag - Added answer link to the bottom of questions with -a/--all flag - Unit tests now managed through Travis CI 1.0 ------ - Added support for Python3 - Switched to the requests library instead of urllib2 - Project status changed to Production/Stable - Added troubleshooting steps to the README 0.2 ------ - Added sane flags - Now using ``/usr/bin/env python`` instead of ``/usr/bin/python`` - Updated README for brew installation instructions 0.1.2 ------ - Added Windows executable - Updated README for pip installation instructions 0.1.1 ------ - Added to PyPI 0.1 ------ - We're doing it live! Keywords: howdoi help console command line answer Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Documentation Description-Content-Type: text/markdown ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/SOURCES.txt0000644000076500000240000000054714316616275017175 0ustar00gleitzstaffCHANGES.txt LICENSE.txt MANIFEST.in README.md fastentrypoints.py requirements.txt setup.py test_howdoi.py howdoi/__init__.py howdoi/__main__.py howdoi/errors.py howdoi/howdoi.py howdoi.egg-info/PKG-INFO howdoi.egg-info/SOURCES.txt howdoi.egg-info/dependency_links.txt howdoi.egg-info/entry_points.txt howdoi.egg-info/requires.txt howdoi.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/dependency_links.txt0000644000076500000240000000000114316616275021351 0ustar00gleitzstaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/entry_points.txt0000644000076500000240000000007614316616275020604 0ustar00gleitzstaff[console_scripts] howdoi = howdoi.howdoi:command_line_runner ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/requires.txt0000644000076500000240000000011514316616275017700 0ustar00gleitzstaffPygments cssselect lxml pyquery requests cachelib appdirs keep rich colorama ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/top_level.txt0000644000076500000240000000000714316616275020032 0ustar00gleitzstaffhowdoi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1627661451.0 howdoi-2.0.20/requirements.txt0000644000076500000240000000014214101022213015550 0ustar00gleitzstaff# when adding a new dependency, also add to setup.py's `install_requires` -r requirements/prod.txt././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1664818366.1108494 howdoi-2.0.20/setup.cfg0000644000076500000240000000004614316616276014142 0ustar00gleitzstaff[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664817974.0 howdoi-2.0.20/setup.py0000644000076500000240000000607014316615466014036 0ustar00gleitzstaff#!/usr/bin/env python import glob import subprocess from pathlib import Path from distutils.cmd import Command # pylint: disable=deprecated-module from setuptools import setup, find_packages # pylint: disable=unused-import import fastentrypoints # noqa: F401 # pylint: enable=unused-import import howdoi class Lint(Command): """A custom command to run Flake8 on all Python source files. """ description = 'run Flake8 on Python source files' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): local_python_files_str = ' '.join(glob.glob('*.py')) commands = {'Flake8': 'flake8 --config=.flake8rc .'.split(), 'Pylint': f'pylint howdoi {local_python_files_str} --rcfile=.pylintrc'.split()} for linter, command in commands.items(): try: print(f'\nRunning {linter}...') subprocess.check_call(command) print(f'No lint errors found by {linter}') except FileNotFoundError: print(f'{linter} not installed') except subprocess.CalledProcessError: pass def read(*names): values = {} for name in names: value = '' for extension in ('.txt', '.md'): filename = name + extension if Path(filename).is_file(): with open(filename, encoding='utf-8') as in_file: value = in_file.read() break values[name] = value return values # pylint: disable=consider-using-f-string long_description = """ %(README)s # News %(CHANGES)s """ % read('README', 'CHANGES') # pylint: enable=consider-using-f-string setup( name='howdoi', version=howdoi.__version__, description='Instant coding answers via the command line', long_description=long_description, long_description_content_type='text/markdown', classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Topic :: Documentation", ], keywords='howdoi help console command line answer', author='Benjamin Gleitzman', author_email='gleitz@mit.edu', maintainer='Benjamin Gleitzman', maintainer_email='gleitz@mit.edu', url='https://github.com/gleitz/howdoi', license='MIT', packages=find_packages(), entry_points={ 'console_scripts': [ 'howdoi = howdoi.howdoi:command_line_runner', ] }, install_requires=[ 'Pygments', 'cssselect', 'lxml', 'pyquery', 'requests', 'cachelib', 'appdirs', 'keep', 'rich', 'colorama' ], cmdclass={ 'lint': Lint } ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664817974.0 howdoi-2.0.20/test_howdoi.py0000644000076500000240000004023014316615466015222 0ustar00gleitzstaff#!/usr/bin/env python """Tests for Howdoi.""" import gzip import json import os import re import unittest from pathlib import Path from unittest.mock import patch import requests from cachelib import NullCache from pyquery import PyQuery as pq # pylint: disable=no-name-in-module from howdoi import howdoi # pylint: disable=protected-access original_get_result = howdoi._get_result def _format_url_to_filename(url, file_ext='html'): filename = ''.join(ch for ch in url if ch.isalnum()) return filename + '.' + file_ext def _get_result_mock(url): # pylint: disable=protected-access file_name = _format_url_to_filename(url, 'html.gz') # pylint: disable=no-member file_path = Path.joinpath(Path(howdoi.HTML_CACHE_PATH), Path(file_name)).resolve() try: with gzip.open(file_path, 'rb') as f: cached_page_content = str(f.read(), encoding='utf-8') return cached_page_content except FileNotFoundError: page_content = original_get_result(url) with gzip.open(file_path, 'wb') as f: f.write(bytes(page_content, encoding='utf-8')) return page_content # pylint: disable=protected-access class HowdoiTestCase(unittest.TestCase): # pylint: disable=too-many-public-methods def setUp(self): self.patcher_get_result = patch.object(howdoi, '_get_result') self.mock_get_result = self.patcher_get_result.start() self.mock_get_result.side_effect = _get_result_mock # ensure no cache is used during testing. howdoi.cache = NullCache() self.queries = ['format date bash', 'print stack trace python', 'convert mp4 to animated gif', 'create tar archive', 'cat'] self.help_queries = howdoi.SUPPORTED_HELP_QUERIES self.pt_queries = ['abrir arquivo em python', 'enviar email em django', 'hello world em c'] self.bad_queries = ['moe', 'mel'] self.query_without_code_or_pre_block = 'Difference between element node and Text Node' def tearDown(self): self.patcher_get_result.stop() keys_to_remove = ['HOWDOI_URL', 'HOWDOI_SEARCH_ENGINE'] for key in keys_to_remove: if key in os.environ: del os.environ[key] howdoi.BLOCKED_ENGINES = [] def _negative_number_query(self): query = self.queries[0] howdoi.howdoi(query + ' -n -1') def _high_positive_number_query(self): query = self.queries[0] howdoi.howdoi(query + ' -n 21') def _negative_position_query(self): query = self.queries[0] howdoi.howdoi(query + ' -p -2') def _high_positive_position_query(self): query = self.queries[0] howdoi.howdoi(query + ' -p 40') def assertValidResponse(self, res): # pylint: disable=invalid-name self.assertTrue(len(res) > 0) def test_get_link_at_pos(self): self.assertEqual(howdoi.get_link_at_pos(['/questions/42/'], 1), '/questions/42/') self.assertEqual(howdoi.get_link_at_pos(['/questions/42/'], 2), '/questions/42/') self.assertEqual(howdoi.get_link_at_pos(['/howdoi', '/questions/42/'], 1), '/howdoi') self.assertEqual(howdoi.get_link_at_pos(['/howdoi', '/questions/42/'], 2), '/questions/42/') self.assertEqual(howdoi.get_link_at_pos(['/questions/42/', '/questions/142/'], 1), '/questions/42/') @patch.object(howdoi, '_get_result') def test_blockerror(self, mock_get_links): mock_get_links.side_effect = requests.HTTPError query = self.queries[0] response = howdoi.howdoi(query) self.assertEqual(response, "ERROR: \x1b[91mUnable to get a response from any search engine\n\x1b[0m") def test_answers(self): for query in self.queries: self.assertValidResponse(howdoi.howdoi(query)) for query in self.bad_queries: self.assertValidResponse(howdoi.howdoi(query)) os.environ['HOWDOI_URL'] = 'pt.stackoverflow.com' for query in self.pt_queries: self.assertValidResponse(howdoi.howdoi(query)) def test_answers_bing(self): os.environ['HOWDOI_SEARCH_ENGINE'] = 'bing' for query in self.queries: self.assertValidResponse(howdoi.howdoi(query)) for query in self.bad_queries: self.assertValidResponse(howdoi.howdoi(query)) os.environ['HOWDOI_URL'] = 'pt.stackoverflow.com' for query in self.pt_queries: self.assertValidResponse(howdoi.howdoi(query)) os.environ['HOWDOI_SEARCH_ENGINE'] = '' # commenting out duckduckgo test, re-enable when issue #404 (duckduckgo blocking requests) is resolved # def test_answers_duckduckgo(self): # os.environ['HOWDOI_SEARCH_ENGINE'] = 'duckduckgo' # for query in self.queries: # self.assertValidResponse(howdoi.howdoi(query)) # for query in self.bad_queries: # self.assertValidResponse(howdoi.howdoi(query)) # os.environ['HOWDOI_URL'] = 'pt.stackoverflow.com' # for query in self.pt_queries: # self.assertValidResponse(howdoi.howdoi(query)) # os.environ['HOWDOI_SEARCH_ENGINE'] = '' def test_answer_links_using_l_option(self): for query in self.queries: response = howdoi.howdoi(query + ' -l') self.assertNotEqual(re.match(r'http.?://.*questions/\d.*', response, re.DOTALL), None) def test_answer_links_using_all_option(self): for query in self.queries: response = howdoi.howdoi(query + ' -a') self.assertNotEqual(re.match(r'.*http.?://.*questions/\d.*', response, re.DOTALL), None) def test_position(self): query = self.queries[0] first_answer = howdoi.howdoi(query) not_first_answer = howdoi.howdoi(query + ' -p5') self.assertNotEqual(first_answer, not_first_answer) def test_all_text(self): query = self.queries[0] first_answer = howdoi.howdoi(query) second_answer = howdoi.howdoi(query + ' -a') self.assertNotEqual(first_answer, second_answer) self.assertNotEqual(re.match('.*Answer from http.?://.*', second_answer, re.DOTALL), None) def test_json_output(self): query = self.queries[0] txt_answer = howdoi.howdoi(query) json_answer = howdoi.howdoi(query + ' -j') link_answer = howdoi.howdoi(query + ' -l') json_answer = json.loads(json_answer)[0] self.assertEqual(json_answer["answer"], txt_answer) self.assertEqual(json_answer["link"], link_answer) self.assertEqual(json_answer["position"], 1) def test_multiple_answers(self): query = self.queries[0] first_answer = howdoi.howdoi(query) second_answer = howdoi.howdoi(query + ' -n3') self.assertNotEqual(first_answer, second_answer) def test_unicode_answer(self): # pylint: disable=no-self-use assert howdoi.howdoi('make a log scale d3') assert howdoi.howdoi('python unittest -n3') assert howdoi.howdoi('parse html regex -a') assert howdoi.howdoi('delete remote git branch -a') def test_colorize(self): query = self.queries[0] normal = howdoi.howdoi(query) colorized = howdoi.howdoi('-c ' + query) # There is currently an issue with Github actions and colorization # so do not run checks if we are running in Github if "GITHUB_ACTION" not in os.environ: self.assertTrue(normal.find('[38;') == -1) self.assertTrue(colorized.find('[38;') != -1) # pylint: disable=line-too-long def test_get_text_without_links(self): html = '''\n

The halting problem is basically a\n formal way of asking if you can tell\n whether or not an arbitrary program\n will eventually halt.

\n \n

In other words, can you write a\n program called a halting oracle,\n HaltingOracle(program, input), which\n returns true if program(input) would\n eventually halt, and which returns\n false if it wouldn't?

\n \n

The answer is: no, you can't.

\n''' # noqa: E501 paragraph = pq(html) expected_output = '''The halting problem is basically a\n formal way of asking if you can tell\n whether or not an arbitrary program\n will eventually halt.\n\n \n \nIn other words, can you write a\n program called a halting oracle,\n HaltingOracle(program, input), which\n returns true if program(input) would\n eventually halt, and which returns\n false if it wouldn't?\n\n \n \nThe answer is: no, you can't.\n\n''' # noqa: E501 actual_output = howdoi.get_text(paragraph) self.assertEqual(actual_output, expected_output) def test_get_text_with_one_link(self): html = '

It\'s a protocol-relative URL (typically HTTP or HTTPS). So if I\'m on http://example.org and I link (or include an image, script, etc.) to //example.com/1.png, it goes to http://example.com/1.png. If I\'m on https://example.org, it goes to https://example.com/1.png.

' # noqa: E501 paragraph = pq(html) expected_output = "It's a [protocol-relative URL](http://paulirish.com/2010/the-protocol-relative-url/) (typically HTTP or HTTPS). So if I'm on http://example.org and I link (or include an image, script, etc.) to //example.com/1.png, it goes to http://example.com/1.png. If I'm on https://example.org, it goes to https://example.com/1.png." # noqa: E501 actual_output = howdoi.get_text(paragraph) self.assertEqual(actual_output, expected_output) def test_get_text_with_multiple_links_test_one(self): html = 'Here\'s a quote from wikipedia\'s manual of style section on links (but see also their comprehensive page on External Links)' # noqa: E501 paragraph = pq(html) expected_output = "Here's a quote from [wikipedia's manual of style](http://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style#Links) section on links (but see also [their comprehensive page on External Links](http://en.wikipedia.org/wiki/Wikipedia:External_links))" # noqa: E501 actual_output = howdoi.get_text(paragraph) self.assertEqual(actual_output, expected_output) def test_get_text_with_multiple_links_test_two(self): html = 'For example, if I were to reference apple.com as the subject of a sentence - or to talk about Apple\'s website as the topic of conversation. This being different to perhaps recommendations for reading our article about Apple\'s website.' # noqa: E501 paragraph = pq(html) expected_output = "For example, if I were to reference [apple.com](http://www.apple.com/) as the subject of a sentence - or to talk about [Apple's website](http://www.apple.com/) as the topic of conversation. This being different to perhaps recommendations for reading [our article about Apple's website](https://ux.stackexchange.com/q/14872/6046)." # noqa: E501 actual_output = howdoi.get_text(paragraph) self.assertEqual(actual_output, expected_output) def test_get_text_with_link_but_with_copy_duplicating_the_href(self): html = 'https://github.com/jquery/jquery/blob/56136897f241db22560b58c3518578ca1453d5c7/src/manipulation.js#L451' # noqa: E501 paragraph = pq(html) expected_output = 'https://github.com/jquery/jquery/blob/56136897f241db22560b58c3518578ca1453d5c7/src/manipulation.js#L451' # noqa: E501 actual_output = howdoi.get_text(paragraph) self.assertEqual(actual_output, expected_output) def test_get_text_with_a_link_but_copy_is_within_nested_div(self): html = 'If the function is from a source file available on the filesystem, then inspect.getsource(foo) might be of help:' # noqa: E501 paragraph = pq(html) expected_output = 'If the function is from a source file available on the filesystem, then [inspect.getsource(foo)](https://docs.python.org/3/library/inspect.html#inspect.getsource) might be of help:' # noqa: E501 actual_output = howdoi.get_text(paragraph) self.assertEqual(actual_output, expected_output) # pylint: enable=line-too-long def test_get_questions(self): links = ['https://stackoverflow.com/questions/tagged/cat', 'http://rads.stackoverflow.com/amzn/click/B007KAZ166', 'https://stackoverflow.com/questions/40108569/how-to-get-the-last-line-of-a-file-using-cat-command'] expected_output = [ 'https://stackoverflow.com/questions/40108569/how-to-get-the-last-line-of-a-file-using-cat-command'] actual_output = howdoi._get_questions(links) self.assertSequenceEqual(actual_output, expected_output) def test_help_queries(self): help_queries = self.help_queries for query in help_queries: output = howdoi.howdoi(query) self.assertTrue(output) self.assertIn('few popular howdoi commands', output) self.assertIn('retrieve n number of answers', output) self.assertIn( 'Specify the search engine you want to use e.g google,bing', output ) def test_missing_pre_or_code_query(self): output = howdoi.howdoi(self.query_without_code_or_pre_block) self.assertTrue(output) self.assertIn('XML elements present in a XML', output) def test_format_url_to_filename(self): url = 'https://stackoverflow.com/questions/tagged/cat' invalid_filename_characters = ['/', '\\', '%'] filename = _format_url_to_filename(url, 'html') self.assertTrue(filename) self.assertTrue(filename.endswith('html')) for invalid_character in invalid_filename_characters: self.assertNotIn(invalid_character, filename) def test_help_queries_are_properly_validated(self): help_queries = self.help_queries for query in help_queries: is_valid_help_query = howdoi._is_help_query(query) self.assertTrue(is_valid_help_query) bad_help_queries = [self.queries[0], self.bad_queries[0], 'use how do i'] for query in bad_help_queries: self.assertFalse(howdoi._is_help_query(query)) def test_negative_and_high_positive_int_values_rejected(self): with self.assertRaises(SystemExit): self._negative_number_query() with self.assertRaises(SystemExit): self._negative_position_query() with self.assertRaises(SystemExit): self._high_positive_position_query() with self.assertRaises(SystemExit): self._high_positive_number_query() class HowdoiTestCaseEnvProxies(unittest.TestCase): def setUp(self): self.temp_get_proxies = howdoi.getproxies def tearDown(self): howdoi.getproxies = self.temp_get_proxies def test_get_proxies1(self): def getproxies1(): proxies = {'http': 'wwwproxy.company.com', 'https': 'wwwproxy.company.com', 'ftp': 'ftpproxy.company.com'} return proxies howdoi.getproxies = getproxies1 filtered_proxies = howdoi.get_proxies() self.assertTrue('http://' in filtered_proxies['http']) self.assertTrue('http://' in filtered_proxies['https']) self.assertTrue('ftp' not in filtered_proxies.keys()) # pylint: disable=consider-iterating-dictionary if __name__ == '__main__': unittest.main()