././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1664818366.110763 howdoi-2.0.20/ 0000755 0000765 0000024 00000000000 14316616276 012321 5 ustar 00gleitz staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664818222.0 howdoi-2.0.20/CHANGES.txt 0000644 0000765 0000024 00000010602 14316616056 014125 0 ustar 00gleitz staff 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! ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1624472669.0 howdoi-2.0.20/LICENSE.txt 0000644 0000765 0000024 00000002067 14064676135 014151 0 ustar 00gleitz staff Copyright (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. ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1631227644.0 howdoi-2.0.20/MANIFEST.in 0000644 0000765 0000024 00000000305 14116507374 014051 0 ustar 00gleitz staff include 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 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1664818366.1103442 howdoi-2.0.20/PKG-INFO 0000644 0000765 0000024 00000037157 14316616276 013433 0 ustar 00gleitz staff Metadata-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:
⚡ Never open your browser to look for help again ⚡
------------------------------------------------------------------------ ## 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 [](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 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664817974.0 howdoi-2.0.20/README.md 0000644 0000765 0000024 00000016330 14316615466 013603 0 ustar 00gleitz staff⚡ Never open your browser to look for help again ⚡
------------------------------------------------------------------------ ## 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 [](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. ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1627661451.0 howdoi-2.0.20/fastentrypoints.py 0000644 0000765 0000024 00000007707 14101022213 016130 0 ustar 00gleitz staff # 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) ././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1664818366.102615 howdoi-2.0.20/howdoi/ 0000755 0000765 0000024 00000000000 14316616276 013612 5 ustar 00gleitz staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664818280.0 howdoi-2.0.20/howdoi/__init__.py 0000644 0000765 0000024 00000000027 14316616150 015711 0 ustar 00gleitz staff __version__ = '2.0.20' ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1634571421.0 howdoi-2.0.20/howdoi/__main__.py 0000644 0000765 0000024 00000000077 14133312235 015671 0 ustar 00gleitz staff from .howdoi import command_line_runner command_line_runner() ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1627661451.0 howdoi-2.0.20/howdoi/errors.py 0000644 0000765 0000024 00000000222 14101022213 015442 0 ustar 00gleitz staff class GoogleValidationError(Exception): pass class BingValidationError(Exception): pass class DDGValidationError(Exception): pass ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664776925.0 howdoi-2.0.20/howdoi/howdoi.py 0000644 0000765 0000024 00000066116 14316475335 015466 0 ustar 00gleitz staff #!/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⚡ Never open your browser to look for help again ⚡
------------------------------------------------------------------------ ## 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 [](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 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/SOURCES.txt 0000644 0000765 0000024 00000000547 14316616275 017175 0 ustar 00gleitz staff CHANGES.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 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/dependency_links.txt 0000644 0000765 0000024 00000000001 14316616275 021351 0 ustar 00gleitz staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/entry_points.txt 0000644 0000765 0000024 00000000076 14316616275 020604 0 ustar 00gleitz staff [console_scripts] howdoi = howdoi.howdoi:command_line_runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/requires.txt 0000644 0000765 0000024 00000000115 14316616275 017700 0 ustar 00gleitz staff Pygments cssselect lxml pyquery requests cachelib appdirs keep rich colorama ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664818365.0 howdoi-2.0.20/howdoi.egg-info/top_level.txt 0000644 0000765 0000024 00000000007 14316616275 020032 0 ustar 00gleitz staff howdoi ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1627661451.0 howdoi-2.0.20/requirements.txt 0000644 0000765 0000024 00000000142 14101022213 015550 0 ustar 00gleitz staff # when adding a new dependency, also add to setup.py's `install_requires` -r requirements/prod.txt ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1664818366.1108494 howdoi-2.0.20/setup.cfg 0000644 0000765 0000024 00000000046 14316616276 014142 0 ustar 00gleitz staff [egg_info] tag_build = tag_date = 0 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664817974.0 howdoi-2.0.20/setup.py 0000644 0000765 0000024 00000006070 14316615466 014036 0 ustar 00gleitz staff #!/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 } ) ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1664817974.0 howdoi-2.0.20/test_howdoi.py 0000644 0000765 0000024 00000040230 14316615466 015222 0 ustar 00gleitz staff #!/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 = '''\nThe 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 \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 \nThe 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
.
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()