├── .coveragerc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md └── PULL_REQUEST_TEMPLATE │ └── pull-request-template.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Demo.gif ├── LICENSE ├── Logo.png ├── README.md ├── autostack ├── __init__.py ├── cli │ ├── __init__.py │ ├── capture.py │ ├── config.py │ ├── constants.py │ ├── display.py │ ├── error.py │ └── init.py ├── error │ ├── __init__.py │ └── __tests__ │ │ ├── mock_handle_exception.py │ │ ├── mock_pipe.py │ │ └── test_error.py ├── main.py ├── pipe │ ├── __init__.py │ └── __tests__ │ │ └── test_pipe.py └── so_web_scraper │ ├── __init__.py │ └── __tests__ │ ├── data │ ├── post_accepted_answer.html │ ├── post_text.html │ ├── post_text_code.html │ ├── post_text_ul_empty.html │ ├── post_text_ul_populated.html │ ├── query_no_post_summaries.html │ └── query_post_summaries.html │ ├── mock_response.py │ └── test_so_web_scraper.py ├── install.sh ├── requirements.txt ├── setup.py └── uninstall.sh /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | *__tests__/* 4 | *main.py* 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-vendored 2 | *.py linguist-vendored=false 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve autostack 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the Bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Open '...' 16 | 2. Input '....' 17 | 3. See error 18 | 19 | **Expected Behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Actual Behavior** 23 | A clear and concise description of what actually happened. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Device Information (please complete the following information)** 29 | - OS: [e.g. Mac OS] 30 | - OS Version [e.g. 10.14.3] 31 | - Python Version [e.g. 2.7] 32 | 33 | **Autostack Version** 34 | 1.0.0 35 | 36 | **Additional Context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull-request-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Description: 5 | 6 | 7 | ## Motivation and Context: 8 | 9 | 10 | 11 | ## Tests: 12 | 13 | 14 | 15 | 16 | ## Screenshots (if appropriate): 17 | 18 | ## Types of changes: 19 | 20 | - [ ] Bug fix (non-breaking change which fixes an issue) 21 | - [ ] New feature (non-breaking change which adds functionality) 22 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 23 | 24 | ## Checklist: 25 | 26 | 27 | - [ ] My code follows the code style of this project. 28 | - [ ] My change requires a change to the documentation. 29 | - [ ] I have updated the documentation accordingly. 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | __pycache__/ 3 | venv/ 4 | .pytest_cache/ 5 | *.pyc 6 | *egg-info 7 | *build 8 | *dist 9 | .coverage 10 | coverage.xml 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | install: 5 | - pip install -r requirements.txt 6 | before_script: 7 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 8 | - chmod +x ./cc-test-reporter 9 | - ./cc-test-reporter before-build 10 | script: 11 | - python -m pytest --cov=autostack --cov-report=xml --pep8 --pylint autostack/ 12 | after_success: 13 | - ./cc-test-reporter after-build -r $COVERAGE_KEY 14 | branches: 15 | only: 16 | - master -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 4 | 5 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 6 | 7 | #### [v1.2.0](https://github.com/autostack-team/autostack/compare/v1.1.1...v1.2.0) 8 | 9 | > 6 December 2019 10 | 11 | - Develop [`#52`](https://github.com/autostack-team/autostack/pull/52) 12 | - Better code structure [`6de0fda`](https://github.com/autostack-team/autostack/commit/6de0fda4db62f436d9049ef4267f21b22a9f3986) 13 | - Working on autostack-cli overhaul [`19354d1`](https://github.com/autostack-team/autostack/commit/19354d19b7c958124594b4e3c34e582789be4601) 14 | - Overhaul of the cli [`47eba88`](https://github.com/autostack-team/autostack/commit/47eba88e2f54c7897841a3ddbdbe710a24ac58d0) 15 | - Update README.md [`d263cf9`](https://github.com/autostack-team/autostack/commit/d263cf954027dd22a51b567ccd9214e8c9ca0b77) 16 | - Comments [`5b23a59`](https://github.com/autostack-team/autostack/commit/5b23a597cd318f852682cb7ebaccc36ff14143b9) 17 | - Bump version number [`57f3c57`](https://github.com/autostack-team/autostack/commit/57f3c57fddf43007c57500805e81ee4d7747357b) 18 | - Remove unneeded bash file [`aadd0bc`](https://github.com/autostack-team/autostack/commit/aadd0bcda4f23ba1a7c64866d2e80a30d4a3475e) 19 | - Pretty-up the code: [`9798d4c`](https://github.com/autostack-team/autostack/commit/9798d4c380311ee7fffbca9e7a2c389ae9760928) 20 | - Updated demo [`9ba3251`](https://github.com/autostack-team/autostack/commit/9ba325116a08f0a1329d925b623502fd71a73c7d) 21 | 22 | #### [v1.1.1](https://github.com/autostack-team/autostack/compare/v1.1.0...v1.1.1) 23 | 24 | > 21 November 2019 25 | 26 | - fix comment [`#47`](https://github.com/autostack-team/autostack/pull/47) 27 | - Bump version number [`551c277`](https://github.com/autostack-team/autostack/commit/551c27763aabd816f1fee133dcade83aa1e4c6d1) 28 | - Update templates [`cad5e72`](https://github.com/autostack-team/autostack/commit/cad5e7260a6d7cece892e937182b55a3e74df5af) 29 | - Clear terminal between posts [`f4c1afd`](https://github.com/autostack-team/autostack/commit/f4c1afde504d38cdf9df900b6b8161abe9cf5a76) 30 | - Update issue templates [`546fe61`](https://github.com/autostack-team/autostack/commit/546fe611c718a2c9e14cad12901ce2ea86f1f7c6) 31 | - Update pull-request-template.md [`2dd7cbf`](https://github.com/autostack-team/autostack/commit/2dd7cbfe7213ac6e567c15d1552c169c8eb634bb) 32 | - Update issue templates [`3ba9606`](https://github.com/autostack-team/autostack/commit/3ba96068d4e10c50f22958a39148c59ef6ba96aa) 33 | 34 | #### [v1.1.0](https://github.com/autostack-team/autostack/compare/v1.0.1...v1.1.0) 35 | 36 | > 15 November 2019 37 | 38 | - Custom Queries! [`#46`](https://github.com/autostack-team/autostack/pull/46) 39 | - Develop [`#44`](https://github.com/autostack-team/autostack/pull/44) 40 | - Fix typo in README [`#43`](https://github.com/autostack-team/autostack/pull/43) 41 | - Custom queries [`a320e2a`](https://github.com/autostack-team/autostack/commit/a320e2a759ba88279fa2f02a0faf52608cb99364) 42 | - Updated tests [`b81fbb7`](https://github.com/autostack-team/autostack/commit/b81fbb7c992b32498d57f54ca14f8a6a1161cb98) 43 | - Updated test cases for handle_user_input [`63d2f31`](https://github.com/autostack-team/autostack/commit/63d2f314021afa21d0e0dff3961502a04c25ed4b) 44 | - Update README to include a demo [`500936a`](https://github.com/autostack-team/autostack/commit/500936ac18c3633732ae7d7db5c5f2c3561ce18a) 45 | - Update README.md [`6faaf43`](https://github.com/autostack-team/autostack/commit/6faaf4305081bc69c1301d1512358ae40a800fdd) 46 | - Updated demo and gitignore [`e16ee7b`](https://github.com/autostack-team/autostack/commit/e16ee7bb53c0fc618d99b1bc8de6cac5c5e69220) 47 | - Update README.md [`d540a2c`](https://github.com/autostack-team/autostack/commit/d540a2c63466c99334e3eee38ded9042e068e30e) 48 | - Update CONTRIBUTING.md [`28d8554`](https://github.com/autostack-team/autostack/commit/28d85543b91c1f283e3df09c296bbdcbb05485bf) 49 | - Update CONTRIBUTING.md [`4f9c289`](https://github.com/autostack-team/autostack/commit/4f9c289fcd04a76aefe5767279b48f8ab38fced5) 50 | - Update CONTRIBUTING.md [`cfbd271`](https://github.com/autostack-team/autostack/commit/cfbd271b2be209f28461aa8724095fb744c9d88c) 51 | - Update README.md [`fd0d8f9`](https://github.com/autostack-team/autostack/commit/fd0d8f97bca8103d1d07a8b89af2ecb026e84ad0) 52 | - Fix typo [`43c5acf`](https://github.com/autostack-team/autostack/commit/43c5acf2dbd912013ea0a7914ad4d99fa3534b2e) 53 | - Update README.md [`f466d2b`](https://github.com/autostack-team/autostack/commit/f466d2bd8c693eff7d8768ef4257400efeeb5da2) 54 | - Bump version number [`fb006a0`](https://github.com/autostack-team/autostack/commit/fb006a0e13b4922979abf92e1958bae84a5c6000) 55 | - Fix typo [`ec092be`](https://github.com/autostack-team/autostack/commit/ec092beed442e7cbf4f0102988a14f80f5c5a0b3) 56 | 57 | #### [v1.0.1](https://github.com/autostack-team/autostack/compare/v1.0.0...v1.0.1) 58 | 59 | > 28 October 2019 60 | 61 | - Update version number [`#42`](https://github.com/autostack-team/autostack/pull/42) 62 | - get_src_code bug fix [`#41`](https://github.com/autostack-team/autostack/pull/41) 63 | - Added more so_web_scraper tests [`#40`](https://github.com/autostack-team/autostack/pull/40) 64 | - Develop [`#39`](https://github.com/autostack-team/autostack/pull/39) 65 | - Develop [`#38`](https://github.com/autostack-team/autostack/pull/38) 66 | - so_web_scraper tests [`#37`](https://github.com/autostack-team/autostack/pull/37) 67 | - Added more so_web_scraper package tests [`#36`](https://github.com/autostack-team/autostack/pull/36) 68 | - Added so_web_scraper package tests [`#35`](https://github.com/autostack-team/autostack/pull/35) 69 | - Develop [`#34`](https://github.com/autostack-team/autostack/pull/34) 70 | - 100% coverage on the error package [`#33`](https://github.com/autostack-team/autostack/pull/33) 71 | - Develop [`#32`](https://github.com/autostack-team/autostack/pull/32) 72 | - Added on so_web_scraper package tests [`70dc64e`](https://github.com/autostack-team/autostack/commit/70dc64e614704bad0794371b59d1a35f228eb80c) 73 | - More error package tests [`43fc217`](https://github.com/autostack-team/autostack/commit/43fc217b6e348f619cce6d37d9393fa89b0a5a36) 74 | - More tests added to so_web_scraper package [`3848b18`](https://github.com/autostack-team/autostack/commit/3848b18f752351cad8a2c6a91a8da7711cc7ea0a) 75 | - Added tests to so_web_scraper package [`d76d74e`](https://github.com/autostack-team/autostack/commit/d76d74ef8ed11e03cb1180372460822da0bcbdf9) 76 | - Update error package tests [`2311a83`](https://github.com/autostack-team/autostack/commit/2311a836c78109f5f940dc20958faf1df2f850e6) 77 | - Testing the error package [`d80679a`](https://github.com/autostack-team/autostack/commit/d80679a311daf8bfd6b089c319df7b142ddab8a0) 78 | - Added tests to so_web_scraper package [`9e2ad4d`](https://github.com/autostack-team/autostack/commit/9e2ad4d4b57ef308ba3501ed697f854aa4e37ed0) 79 | - Added tests to so_web_scraper package [`bc5bb93`](https://github.com/autostack-team/autostack/commit/bc5bb93602e98ee4dd8a5e09768681b1668d7222) 80 | - More error package tests [`a885761`](https://github.com/autostack-team/autostack/commit/a885761ea4cc01792b4baf12736f0e34bf581416) 81 | - Error package tests cleanup [`65175e3`](https://github.com/autostack-team/autostack/commit/65175e39eef78283fae054d3793b4173cd039cad) 82 | - Update README.md [`edfafed`](https://github.com/autostack-team/autostack/commit/edfafed05199c4b68eee438a61f2cf23d01e935c) 83 | - Updated so_web_scraper tests [`5b2d96c`](https://github.com/autostack-team/autostack/commit/5b2d96c31881891a14a5529216be1e8ce661aca0) 84 | - Update README.md [`a27a9c5`](https://github.com/autostack-team/autostack/commit/a27a9c52142a96963c6c0344918b8477218bb9af) 85 | - Update README.md [`b7ab7a0`](https://github.com/autostack-team/autostack/commit/b7ab7a0b93fe09b21087462abb5b01ebc3b4cc89) 86 | 87 | #### v1.0.0 88 | 89 | > 22 October 2019 90 | 91 | - Wrote pipe package tests, and refactored main and create_pipe [`#31`](https://github.com/autostack-team/autostack/pull/31) 92 | - Updated .travis.yml [`#30`](https://github.com/autostack-team/autostack/pull/30) 93 | - Develop [`#28`](https://github.com/autostack-team/autostack/pull/28) 94 | - Working on testing [`#27`](https://github.com/autostack-team/autostack/pull/27) 95 | - Pylint code cleanup [`#26`](https://github.com/autostack-team/autostack/pull/26) 96 | - Finished so_web_scraper refactoring [`#25`](https://github.com/autostack-team/autostack/pull/25) 97 | - Develop [`#24`](https://github.com/autostack-team/autostack/pull/24) 98 | - print post text and print code block cleanup [`#23`](https://github.com/autostack-team/autostack/pull/23) 99 | - Develop [`#22`](https://github.com/autostack-team/autostack/pull/22) 100 | - Develop [`#21`](https://github.com/autostack-team/autostack/pull/21) 101 | - Develop [`#20`](https://github.com/autostack-team/autostack/pull/20) 102 | - Develop [`#19`](https://github.com/autostack-team/autostack/pull/19) 103 | - Bug fix when 'span' tags are wrapped in other tags in a 'code' tag in print_code_block [`#18`](https://github.com/autostack-team/autostack/pull/18) 104 | - Develop [`#17`](https://github.com/autostack-team/autostack/pull/17) 105 | - Unit tests, travis, and emoji fix. [`#16`](https://github.com/autostack-team/autostack/pull/16) 106 | - Improving Stack Overflow Querying [`#12`](https://github.com/autostack-team/autostack/pull/12) 107 | - Pypi [`#11`](https://github.com/autostack-team/autostack/pull/11) 108 | - PyPi [`#10`](https://github.com/autostack-team/autostack/pull/10) 109 | - Remove D scripts [`#9`](https://github.com/autostack-team/autostack/pull/9) 110 | - Create LICENSE [`#8`](https://github.com/autostack-team/autostack/pull/8) 111 | - Added comments [`#7`](https://github.com/autostack-team/autostack/pull/7) 112 | - Added test cases [`#6`](https://github.com/autostack-team/autostack/pull/6) 113 | - Added get_post_url functionality [`#5`](https://github.com/autostack-team/autostack/pull/5) 114 | - Cli [`#3`](https://github.com/autostack-team/autostack/pull/3) 115 | - Added SOQuery [`#2`](https://github.com/autostack-team/autostack/pull/2) 116 | - Added Initial Parsing Stuff [`#1`](https://github.com/autostack-team/autostack/pull/1) 117 | - CLI [`4e8ed2a`](https://github.com/autostack-team/autostack/commit/4e8ed2a1b91872d7c7cda0eb79994d6ea7cd2ba1) 118 | - Remove venv [`ce0a18a`](https://github.com/autostack-team/autostack/commit/ce0a18a8796448d65533849e6a0cb0f682a35a3f) 119 | - Working on unit tests [`277beba`](https://github.com/autostack-team/autostack/commit/277bebaedefd3f552a4b048856e4cf3902dafce8) 120 | - Testing [`13a8e27`](https://github.com/autostack-team/autostack/commit/13a8e27c618da50f163257765d57bc64f56b568d) 121 | - Complete overhaul of the Stack Overflow web scraping [`a91a975`](https://github.com/autostack-team/autostack/commit/a91a97573545a2f3267a97d092e4cd1955ef12c1) 122 | - Removed StackoverflowScraper class [`e3e2b6f`](https://github.com/autostack-team/autostack/commit/e3e2b6f060d78e2750327ec90099e8f57177bb92) 123 | - Working on improving SO searches [`6bc3445`](https://github.com/autostack-team/autostack/commit/6bc34458e00d40ec1fca142ba224c1becbfb4fe0) 124 | - Code refactoring & project reorganization [`fccf751`](https://github.com/autostack-team/autostack/commit/fccf7516ffc7d26189d01fe5f536242aa4418f78) 125 | - Unit tests skeleton, travis CI setup, and emoji py2 fix [`6fd5800`](https://github.com/autostack-team/autostack/commit/6fd580000e2b55c54048ea235ce9a909be25842e) 126 | - error package bug fixes and so_web_scraper refactoring [`87258d1`](https://github.com/autostack-team/autostack/commit/87258d18bc7cf96482a26cf9e7ac21118d4c1834) 127 | - Done...maybe? Probabaly not [`34a9c22`](https://github.com/autostack-team/autostack/commit/34a9c22964a862bd0cf95706d3a201ce5317052d) 128 | - Cleaned up SOQuery and autostack [`8a4cee4`](https://github.com/autostack-team/autostack/commit/8a4cee4c2760a1fab1c19965b81cf2ab12b1dbcd) 129 | - Refactoring [`a0434d6`](https://github.com/autostack-team/autostack/commit/a0434d69f64da6326b164e8de5bc9bdf917704b8) 130 | - Refactoring [`19185e4`](https://github.com/autostack-team/autostack/commit/19185e46a0bca0a7d9fe8c8b857c8ed4c5b18d91) 131 | - Finished scraper tests [`5762cc3`](https://github.com/autostack-team/autostack/commit/5762cc382632216cfc14dbf9bb264fe609a8441e) 132 | - Error and exception handling [`70690ed`](https://github.com/autostack-team/autostack/commit/70690ed6a9227b4d2fe76e4d0448a0b5abff3f71) 133 | - Finished the initial setup of removing the D scripts [`5adb8ab`](https://github.com/autostack-team/autostack/commit/5adb8ab9ee8c0b5efab9d5b3c1021a96ad60bc70) 134 | - Removing the old method of monitoring errors with D scripts [`42c1582`](https://github.com/autostack-team/autostack/commit/42c1582e9afe6ebd5375f7acdaecc7a09f2a078a) 135 | - CHANGELOG update [`a00ee5e`](https://github.com/autostack-team/autostack/commit/a00ee5efbedbefb021901c49e27e75b01b896eed) 136 | - Working on main tests [`a9e8733`](https://github.com/autostack-team/autostack/commit/a9e873323c352f59203dd0a14ae6b8e89b8c0909) 137 | - Create CODE_OF_CONDUCT.md [`6b262ac`](https://github.com/autostack-team/autostack/commit/6b262ac34091eacc0e8ccbd11b84f65534d16410) 138 | - Building web scraping functions [`8d1a414`](https://github.com/autostack-team/autostack/commit/8d1a414eb21dcad4a1a863ff1a1c0cf7ac88f5a5) 139 | - Working on unit tests and travis setup [`8fc1eed`](https://github.com/autostack-team/autostack/commit/8fc1eedcfd9a89d5070dec336686fcf99972ac12) 140 | - Focused on main errors [`46729a6`](https://github.com/autostack-team/autostack/commit/46729a638776c34c83fc8e4d3b1ec1dfcdbd93c9) 141 | - Create issue template [`6ed338a`](https://github.com/autostack-team/autostack/commit/6ed338aff06347ee69ea21a830353c29c65504c5) 142 | - Python 2 support and PyPi setup changes [`0fc67de`](https://github.com/autostack-team/autostack/commit/0fc67de1699509e0ffe357e3c75edde08f3fcfd7) 143 | - Create CONTRIBUTING.md [`cb34b58`](https://github.com/autostack-team/autostack/commit/cb34b58af79d6562145e4a3151e93c5dc5f33fac) 144 | - Working on PyPi description [`387f564`](https://github.com/autostack-team/autostack/commit/387f564cf3712419ff4c28f6107bbef6b372cd12) 145 | - Reduced print_code_block complexity [`916657e`](https://github.com/autostack-team/autostack/commit/916657e42762d9d416ed984e5761512df6bd623e) 146 | - Syntax highlighting [`60479e4`](https://github.com/autostack-team/autostack/commit/60479e4c8e7a571ea3ed7649704ed202cf5dd667) 147 | - Populated CHANGELOG.md [`9a8cdac`](https://github.com/autostack-team/autostack/commit/9a8cdacc06f5c66817145c89a3ff0255f4c0ffa1) 148 | - Update README.md [`28918fd`](https://github.com/autostack-team/autostack/commit/28918fd39bcdc7597b53451bc658646aa50d6035) 149 | - Removed features package [`7fbe92b`](https://github.com/autostack-team/autostack/commit/7fbe92bb282af05a2eabdc53412a3a9f5fd6169f) 150 | - Added error printing and handling [`348eb47`](https://github.com/autostack-team/autostack/commit/348eb479ed4dd18d3d890119f8f52544e9a66ebd) 151 | - Added custom query function and option/features class [`97cfbf8`](https://github.com/autostack-team/autostack/commit/97cfbf86660edd5572f3e69dc1643d9340b4abf5) 152 | - Refactoring so_web_scraper [`5a0eccb`](https://github.com/autostack-team/autostack/commit/5a0eccbecc293d31f093b5f64cf7761f4ff9c141) 153 | - Finished web scraping and html parsing [`7fa5cdf`](https://github.com/autostack-team/autostack/commit/7fa5cdf95508e0da24ec218a79e1ccbcbc66bea0) 154 | - PEP8 Styling Fixes [`7cdeb4b`](https://github.com/autostack-team/autostack/commit/7cdeb4b20c006266025b2a36c6955a05a98b042e) 155 | - Update readme and change imports [`0db19b2`](https://github.com/autostack-team/autostack/commit/0db19b2832fdc9fb0c87fc4b8e91dfcbf672d4a5) 156 | - Refactoring so_web_scraper [`5987bfa`](https://github.com/autostack-team/autostack/commit/5987bfabaf9164277bc49e4735c13490cd81822d) 157 | - PEP8 fixes and code cleanup [`9937c2c`](https://github.com/autostack-team/autostack/commit/9937c2c0cd4394b35e0980778f9d9b4dce60d965) 158 | - Updated CHANGELOG.md [`31cef7b`](https://github.com/autostack-team/autostack/commit/31cef7b538d28ada3c0256d3ec26d20e37cdf4e1) 159 | - Delete bug_report.md [`cec457e`](https://github.com/autostack-team/autostack/commit/cec457ef0e5f870866da9ee99183d9f5772f3719) 160 | - Added ability to loop over posts [`089a632`](https://github.com/autostack-team/autostack/commit/089a6323c27f8f0ea8764786a39dc8cada142f22) 161 | - More PEP8 Fixes [`cb9d2c6`](https://github.com/autostack-team/autostack/commit/cb9d2c6d5df222d5102774fefc98ea520d9a2c9e) 162 | - Grammar fix in comments, and maintainability badge [`03097fb`](https://github.com/autostack-team/autostack/commit/03097fb76e9a622b2a2d16c7bcb91bb0b601eec6) 163 | - Added pylint extension to pytest [`4b93147`](https://github.com/autostack-team/autostack/commit/4b93147a5a3ae24465607c44429328dedd3a5ad3) 164 | - Refactoring [`42790b4`](https://github.com/autostack-team/autostack/commit/42790b488dbc8fbd03e3d14d04c17101aec6e48e) 165 | - Create pull_request_template.md [`691c2dc`](https://github.com/autostack-team/autostack/commit/691c2dc7fde3eea5dc1d74278f53d2ade5ac9fc0) 166 | - Refactored listen_for_errors [`4bbfd93`](https://github.com/autostack-team/autostack/commit/4bbfd93e61cfbe83477f8ca7087bfe5725401944) 167 | - Update README.md [`29eaf71`](https://github.com/autostack-team/autostack/commit/29eaf713cec20d9bc2b3865797e86a5443b47ae3) 168 | - Added request exception/error handling [`bd1e10b`](https://github.com/autostack-team/autostack/commit/bd1e10bbe0001ef37bc23cfe12a6a0ea3792a470) 169 | - Formatted request handling responses and code cleanup [`1961e2e`](https://github.com/autostack-team/autostack/commit/1961e2e1b3950232b8ee62b5e196feb81ba2f91c) 170 | - Update README.md [`47759b9`](https://github.com/autostack-team/autostack/commit/47759b9228782105037518a000f35c5619437e2e) 171 | - Bug fix when 'span' tags are wrapped in other tags in a 'code' tag [`6e5dbe6`](https://github.com/autostack-team/autostack/commit/6e5dbe602402019e3024e0d05dd191da3a300458) 172 | - Create issue templates [`411e54b`](https://github.com/autostack-team/autostack/commit/411e54b682d26c85c63a3888e90368a43f7ede89) 173 | - Added setup.py [`bf5d059`](https://github.com/autostack-team/autostack/commit/bf5d059c861da7dfb14fc8872cd67f58e4961c42) 174 | - Final PEP8 Style Fixes [`69a6176`](https://github.com/autostack-team/autostack/commit/69a6176d98162b7daa41b3d949141cb1972e780c) 175 | - Fixed extra space from line break in print [`e9a9f26`](https://github.com/autostack-team/autostack/commit/e9a9f267c4841c4b9dd4b831ce4a1c1a97619113) 176 | - Setting up custom query [`0a9790c`](https://github.com/autostack-team/autostack/commit/0a9790c43c7d0ba45bdc214f30a3a1ef9f5f665c) 177 | - Added CHANGELOG.md [`6b180c8`](https://github.com/autostack-team/autostack/commit/6b180c8daaed227f1d356f0fa9ddac59a27290e2) 178 | - autostack-terminal now works with both Mac and Linux [`c076010`](https://github.com/autostack-team/autostack/commit/c076010c7e341a78173e19791db3eb23720e5356) 179 | - Updated CHANGELOG.md [`91181c0`](https://github.com/autostack-team/autostack/commit/91181c0d3406907a677f8d3273007fa2f12d4bd8) 180 | - Fixed the install script, input error, and script command error [`465ba54`](https://github.com/autostack-team/autostack/commit/465ba54b5eb4e28ab7b5410a2415704fe92eea54) 181 | - Update README.md [`1d63094`](https://github.com/autostack-team/autostack/commit/1d6309403fd45072886a816682a0d177474329c2) 182 | - Moved printed query to new line [`be12c14`](https://github.com/autostack-team/autostack/commit/be12c149dc7125815124ba2cee1dc174720758ac) 183 | - Deleted function that's no longer needed [`584accf`](https://github.com/autostack-team/autostack/commit/584accf874d0a30ef6696bd6d2f7af61fdfe2810) 184 | - Updated pull request template [`52da428`](https://github.com/autostack-team/autostack/commit/52da4282a447540c7273428fcdf0ea53089f25bc) 185 | - Create LICENSE.md [`a6cb9fa`](https://github.com/autostack-team/autostack/commit/a6cb9fa8e9e08c71d440f9ce8b71f50abbdf52de) 186 | - Code cleanup [`e8f28bb`](https://github.com/autostack-team/autostack/commit/e8f28bb717bd1945da297dceec6c44a3da1a1aea) 187 | - Delete LICENSE.md [`ed38241`](https://github.com/autostack-team/autostack/commit/ed38241ac19c84c8ad6f805a39de6f9cc36add2a) 188 | - Code cleanup and comments [`abd3255`](https://github.com/autostack-team/autostack/commit/abd3255a0a1ff7df9098aed8c00700e7b5114e73) 189 | - Added methods [`fe598dd`](https://github.com/autostack-team/autostack/commit/fe598dd0990324d5f028ac131d3bff5c221af9fb) 190 | - Update README.md [`085bc76`](https://github.com/autostack-team/autostack/commit/085bc7642ab7315d10d3cb76951a894e6b6d5440) 191 | - Updated documentation [`aea0fbb`](https://github.com/autostack-team/autostack/commit/aea0fbb59652fc6f83c1e8a4c4335ec5aefb6bef) 192 | - Fixed import bug and syntax error [`5bf400e`](https://github.com/autostack-team/autostack/commit/5bf400e4a4c097d984c9b551ae0e6c81e95b2fce) 193 | - Update README.md [`61366f0`](https://github.com/autostack-team/autostack/commit/61366f0f824e662c53a2e45b74c7e2dbd36aa344) 194 | - install script [`6f01dda`](https://github.com/autostack-team/autostack/commit/6f01dda0e193f6bfad27752c1bd14c410407a851) 195 | - Delete unecessary code [`e0be292`](https://github.com/autostack-team/autostack/commit/e0be292fbeb464e80373ccbc52476bb7aa4d5190) 196 | - Fixing Timeout error catching [`bbba562`](https://github.com/autostack-team/autostack/commit/bbba56222bd2b9e6170a629a100d105d18f1e8cc) 197 | - Added pygments and tested on new page [`9ab7fa1`](https://github.com/autostack-team/autostack/commit/9ab7fa14873b692fab4e5802ba9403e1ac88665c) 198 | - Updated shields on README.md [`6648147`](https://github.com/autostack-team/autostack/commit/66481477f17fed84f95065a591d5650df0fc3b50) 199 | - Update README.md [`8ef3ffe`](https://github.com/autostack-team/autostack/commit/8ef3ffe4450f9c255ab59f6dcce58d035eaf64f1) 200 | - Update README.md [`2cba78c`](https://github.com/autostack-team/autostack/commit/2cba78c2bfef235541a8aa4a7ee7c0b1675351c3) 201 | - Added ability to exit from custom query [`48aafb8`](https://github.com/autostack-team/autostack/commit/48aafb825e731447880287c49dd97339cfb0b375) 202 | - Fix print error and moved request.get [`6f5e336`](https://github.com/autostack-team/autostack/commit/6f5e33699c7ca0f1ed1ed451d2850319119f3038) 203 | - Fixed bug [`57a6d1f`](https://github.com/autostack-team/autostack/commit/57a6d1f88245b96bbf66167fe42b6c3592ca3c93) 204 | - Updated slack badge [`4eaefe7`](https://github.com/autostack-team/autostack/commit/4eaefe7d0b662fcc8b2d349d6a39b5725b59163d) 205 | - Updates to pull request template [`7ca93ba`](https://github.com/autostack-team/autostack/commit/7ca93bacfd4bf11a02aedec1499433d791c8bcb3) 206 | - Updated CHANGELOG.md [`7067c38`](https://github.com/autostack-team/autostack/commit/7067c384192131cfcf02aca344286eb2a30bb046) 207 | - Adding Test Script [`72ea967`](https://github.com/autostack-team/autostack/commit/72ea9678f201a3799d41282055b2443d146a81ff) 208 | - Refactoring [`766d4d4`](https://github.com/autostack-team/autostack/commit/766d4d4eeabfc00ddbfbb54e224a1dd711976f35) 209 | - Requirements [`65eb716`](https://github.com/autostack-team/autostack/commit/65eb7166dae4ada3ac11b3671b02111433377700) 210 | - Updated chat badge to MSTeams [`cd0f684`](https://github.com/autostack-team/autostack/commit/cd0f684e10ee2f50c7cb25894b794c52dcbf0786) 211 | - Update README.md [`16eddb4`](https://github.com/autostack-team/autostack/commit/16eddb43677988620c0d6f62228756544338be35) 212 | - Update README.md [`e9d3fa1`](https://github.com/autostack-team/autostack/commit/e9d3fa19dd9cd0fc4d5f96b4fba1bd8ea5f1d056) 213 | - Added patreon shield [`fa0080a`](https://github.com/autostack-team/autostack/commit/fa0080a49594415fe14e7f8ad2b98a601a40042c) 214 | - Update README.md [`c165f34`](https://github.com/autostack-team/autostack/commit/c165f3428f4fcda3bc7b9b4fbeab41d1a1805d4a) 215 | - Added get_post_url funcitonality [`5400b2f`](https://github.com/autostack-team/autostack/commit/5400b2ff939571b0bdf82cb5fbd549e6ea434464) 216 | - Fixed slack shield [`bb605aa`](https://github.com/autostack-team/autostack/commit/bb605aa67069dedccff247aaba6051208af646e0) 217 | - Removed StackoverflowScraper class [`6c6105d`](https://github.com/autostack-team/autostack/commit/6c6105d4823698b446aa8e7bb7ba058e0d97382d) 218 | - Bug fix: module not found [`57c3f11`](https://github.com/autostack-team/autostack/commit/57c3f115f6c0dfdaca6bac770b117053b8d97d09) 219 | - Update README.md [`3a95608`](https://github.com/autostack-team/autostack/commit/3a956089cb5ecbd6c4336b280950e51da2e0e32c) 220 | - Python 2 support bug fix [`3972ec9`](https://github.com/autostack-team/autostack/commit/3972ec94650a4c7b3417729e29f25ad4c644f4f3) 221 | - 🥞 [`8cb2f71`](https://github.com/autostack-team/autostack/commit/8cb2f717ba7ea592ef8d89ce4123337b4e47a987) 222 | - Update .gitattributes [`6ec1504`](https://github.com/autostack-team/autostack/commit/6ec1504b33aa678b3879929a656ef1377cf5aec6) 223 | - Update Travis CI and .gitignore [`7585ec2`](https://github.com/autostack-team/autostack/commit/7585ec24f557023812f8efecead6e2b5942cb70a) 224 | - Update README.md [`dea0e2a`](https://github.com/autostack-team/autostack/commit/dea0e2a616880ba45ff089da48748a2696b755a3) 225 | - Added twitter shield [`aed4cfc`](https://github.com/autostack-team/autostack/commit/aed4cfc3a4fcec45ff12ccf3369f31172697f399) 226 | - Updated CHANGELOG.md [`d7cb4bc`](https://github.com/autostack-team/autostack/commit/d7cb4bc7a03b8c0fd8f308769c392e98f502ccbb) 227 | - Update .gitignore [`99e8da7`](https://github.com/autostack-team/autostack/commit/99e8da7e630cae627e9ca5a22a616c96f3b020c3) 228 | - Delete settings.json [`c0ac60a`](https://github.com/autostack-team/autostack/commit/c0ac60a889f23435356c83ca629e33da8d451bc1) 229 | - Update .gitattributes [`402991b`](https://github.com/autostack-team/autostack/commit/402991bd5f58c3eb77892dd586d2a7f7fd3feaaf) 230 | - Updated code_of_conduct email [`3865e49`](https://github.com/autostack-team/autostack/commit/3865e4921be4b65de4d94afa3341f3073973dbdd) 231 | - Fixed email [`ef78d61`](https://github.com/autostack-team/autostack/commit/ef78d6188196867aefcca8297cea81c2b683cc80) 232 | - Update Travis [`fc51bf0`](https://github.com/autostack-team/autostack/commit/fc51bf0890088f82f757c0a527a3b225c836af68) 233 | - Code cleanup [`f2adf4f`](https://github.com/autostack-team/autostack/commit/f2adf4f12fc60b209f828df06847681ea48c7981) 234 | - Update README.md [`243de42`](https://github.com/autostack-team/autostack/commit/243de42778e491ec918acc96dd19d68a53abd626) 235 | - Fixed incorrect OS in setup [`79c9134`](https://github.com/autostack-team/autostack/commit/79c91347617ae2caf8a3d277a7ca75649c0cd2bc) 236 | - Update README.md [`93229db`](https://github.com/autostack-team/autostack/commit/93229db9c59f5d12464851bd8a77c6bec0a5526e) 237 | - Update .gitattributes [`010a830`](https://github.com/autostack-team/autostack/commit/010a8307169059a3826f3e784718c845623c713c) 238 | - Update README.md [`e05a56d`](https://github.com/autostack-team/autostack/commit/e05a56dc3259682e78ffad3712cc23c2a9a02cd3) 239 | - File renamed [`07ac871`](https://github.com/autostack-team/autostack/commit/07ac87140fc82d90e39752bf409dbdec7807ba82) 240 | - Update version number [`91021c2`](https://github.com/autostack-team/autostack/commit/91021c206ec1a2d35624a23a92c205b173a40b1f) 241 | - Create .gitattributes [`f37c764`](https://github.com/autostack-team/autostack/commit/f37c764b0f99cce7a620ebed94ecbc2f2644424b) 242 | - Updated PyPi version. [`f3aac2a`](https://github.com/autostack-team/autostack/commit/f3aac2ac41be9cb170eb7639eab79dd9b3adef9c) 243 | - Update README.md [`70e1dfa`](https://github.com/autostack-team/autostack/commit/70e1dfaef2298345579437932d8d7441f11682af) 244 | - Update coveragerc file [`8533cfd`](https://github.com/autostack-team/autostack/commit/8533cfde17810c7bb1035ba8d01dd6635f9081c6) 245 | - Update .gitattributes [`740c7e2`](https://github.com/autostack-team/autostack/commit/740c7e285c1b444dcf619e031f839490cd45c03b) 246 | - Update .gitattributes [`c95d4a5`](https://github.com/autostack-team/autostack/commit/c95d4a527afa61bc18f705cf46021e95b7f25aae) 247 | - Update README.md [`881ae72`](https://github.com/autostack-team/autostack/commit/881ae72a56308198961021d35c3298546e6604e9) 248 | - Change to Travis file [`c8e24d2`](https://github.com/autostack-team/autostack/commit/c8e24d2bfb2a066028db0398df5f430508a01b7d) 249 | - first commit [`7833100`](https://github.com/autostack-team/autostack/commit/7833100c90417c53834df4e77beee2687a872484) 250 | - Added slack shield [`b2376fd`](https://github.com/autostack-team/autostack/commit/b2376fd57c495372de3622db879a5ab188fe2023) 251 | - Updated .gitignore [`4a7726b`](https://github.com/autostack-team/autostack/commit/4a7726b5ce41e39aed8dd7bee8d248253b9e5e30) 252 | - Updated logo [`0989cc8`](https://github.com/autostack-team/autostack/commit/0989cc883055f68ddf5a9873ca72231fae4e00d4) 253 | - Updated logo [`9466d6f`](https://github.com/autostack-team/autostack/commit/9466d6f23b4f7700b072acbf250459fae4844e7c) 254 | - Rename feature req [`94e0478`](https://github.com/autostack-team/autostack/commit/94e0478a479d21bb418ce17fb1fcd3c93f02b40b) 255 | - Proper python project directory name [`08ec908`](https://github.com/autostack-team/autostack/commit/08ec908ed21b8c8840ecc4f42bd77b03606f6b3c) 256 | - Added logo [`abe05b8`](https://github.com/autostack-team/autostack/commit/abe05b8625e1d31e54aaacf7793fa48e5a90639f) 257 | - Rename pull request template [`5a46ac3`](https://github.com/autostack-team/autostack/commit/5a46ac340e7df356ef7ae76b4590b3d3cab3073c) 258 | - Added PULL_REQUEST_FOLDER and featuer_request [`f444bce`](https://github.com/autostack-team/autostack/commit/f444bced1d4cbe6b2dc8c37d33e22e86b3e246a6) 259 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at autostackteam@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ### Thank you for considering contributing to autostack! 4 | 5 | It's people like you that make autostack such a great tool. 6 | 7 | ### Why you should read these guidelines. 8 | 9 | Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests. 10 | 11 | ### What kinds of contributions we are looking for. 12 | 13 | Keep an open mind! There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests, or writing code which can be incorporated into autostack itself. 14 | 15 | # Ground rules 16 | Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all of your interactions with the project. 17 | 18 | Responsibilities: 19 | * Ensure that code follows the PEP 8 style guide. 20 | * Do not create new classes, if possible. Try to use functions. 21 | * Create issues for any major changes and enhancements that you wish to make. 22 | * Please, don't use the issue tracker for support questions. 23 | * Keep feature versions as small as possible, preferably one new feature per version. 24 | 25 | # Your first contribution 26 | 27 | Unsure where to begin contributing to autostack? You can start by looking through beginner and help-wanted issues. 28 | * Beginner issues - issues which should only require a few lines of code, and a test or two. 29 | * Help wanted issues - issues which should be a bit more involved than beginner issues. 30 | 31 | # Getting started 32 | 33 | To contribute, find an issue, or open an issue, that you want to work on such as a feature request or a bug fix: 34 | 35 | 1. Create your own fork of the code. 36 | 2. Make the change in your fork. 37 | 3. Create a pull request. 38 | 39 | Before submitting a contribution, be sure that you have followed all of the ground rules. 40 | 41 | Unlike larger contributions, smaller contributions such as spelling errors or comment cleanup do not require an issue. Simply fork the code and create a pull request. 42 | 43 | # How to report a bug 44 | 45 | If you find a security vulnerability, do **NOT** open an issue. Email autostackteam@gmail.com instead. 46 | 47 | Follow the [template](.github/ISSUE_TEMPLATES/bug-report.md) provided. 48 | 49 | # How to suggest a feature or enhancement 50 | 51 | Follow the [template](.github/ISSUE_TEMPLATES/feature-request.md) provided. 52 | 53 | # Code review process 54 | 55 | The core team looks at pull requests on a regular basis in a weekly meeting. 56 | 57 | After we have given feedback, we expect responses within two weeks. After two weeks, we will close the pull request if it isn't showing any activity. 58 | 59 | # Community 60 | 61 | You can chat with the core team on [Gitter](https://gitter.im/autostack-team/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link). 62 | -------------------------------------------------------------------------------- /Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autostack-team/autostack/a96eb87c0a210d5a0d46257d1f64445622c5ee16/Demo.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Elijah Sawyers 4 | Copyright (c) 2019 Benjamin Sanders 5 | Copyright (c) 2019 Caleb Werth 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autostack-team/autostack/a96eb87c0a210d5a0d46257d1f64445622c5ee16/Logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://raw.githubusercontent.com/autostack-team/autostack/develop/Logo.png) 2 | 3 |

4 | 5 | Build status 7 | 8 | 9 | Code coverage 11 | 12 | 13 | Maintainability 15 | 16 | 17 | GitHub commit activity 19 | 20 | 21 | PyPI - Downloads 23 | 24 | 25 | PyPI 27 | 28 | 29 | License 31 | 32 | 33 | Chat 35 | 36 | 37 | Follow on Twitter 39 | 40 | 41 | Support on Patreon 43 | 44 | 45 | Logo designer's instagram 47 | 48 |

49 | 50 | autostack is a command-line debugging tool for Python projects that automatically displays Stack Overflow answers for thrown errors. 51 | 52 | What is the first thing you do when a confusing error message is displayed in your terminal window? You search for an answer on Stack Overflow, of course! With autostack, you no longer have to search for answers on Stack Overflow, they are found for you. Gone are the days of scouring the internet for hours to find an answer to your development questions! autostack is here to automate the debugging process and in turn, expedite Python project development. 53 | 54 | ## Table of Contents 55 | 56 | * [Installation](#Installation) 57 | * [Usage](#Usage) 58 | * [Demo](#Demo) 59 | * [Contributing](#Contributing) 60 | * [License](#License) 61 | * [Show your support](#Show-your-support) 62 | * [Authors](#Authors) 63 | 64 | ## Installation 65 | 66 | **1. Clone the repo and use the install script.** 67 | 68 | Clone the repo. 69 | 70 | ```sh 71 | git clone https://github.com/autostack-team/autostack.git 72 | ``` 73 | 74 | Navigate to the project directory, and run the install bash script. 75 | 76 | ```sh 77 | cd /path/to/project/ 78 | chmod +x install.sh 79 | ./install.sh 80 | ``` 81 | 82 | **2. Or just use pip to install.** 83 | 84 | ```sh 85 | pip3 install autostack 86 | ``` 87 | 88 | ## Usage 89 | 90 | In one terminal window, execute "autostack capture" which will capture all errors in the terminal. You can run this command in as many terminal windows as you'd like. 91 | 92 | ```sh 93 | autostack capture 94 | ``` 95 | 96 | In another terminal window, execute "autostack display" to display Stack Overflow posts for all captured errors. 97 | 98 | ```sh 99 | autostack display 100 | ``` 101 | 102 | To stop running autostack, use the exit command in the terminals that executed "autostack capture". This automatically stops the terminal window displaying Stack Overflow posts for captured errors. 103 | 104 | ```sh 105 | exit 106 | ``` 107 | 108 | ## Demo 109 | 110 | Checkout the demo below! 111 | 112 |

113 | Demo 115 |

116 | 117 | ## Contributing 118 | 119 | For information on how to get started contributing to autostack, see the [contributing guidlines](https://github.com/autostack-team/autostack/blob/master/CONTRIBUTING.md). 120 | 121 | ## License 122 | 123 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 124 | 125 | ## Show your support 126 | 127 | Give a ⭐️ if autostack has helped you! 128 | 129 | 130 | 131 | 132 | 133 | ## Authors 134 | * [Elijah Sawyers](https://github.com/elijahsawyers) 135 | * [Benjamin Sanders](https://github.com/BenOSanders) 136 | * [Caleb Werth](https://github.com/cwerth1) 137 | -------------------------------------------------------------------------------- /autostack/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name, line-too-long, anomalous-backslash-in-string 2 | ''' 3 | Authors: Elijah Sawyers, Benjamin Sanders 4 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 5 | Date: 03/17/2019 6 | Overview: The autostack package includes a cli and packages for error 7 | parsing, querying Stack Exchange, etc. 8 | ''' 9 | 10 | name = 'autostack' 11 | 12 | 13 | def print_logo(): 14 | ''' 15 | Prints the autostack logo, in color. 16 | ''' 17 | 18 | print(u' \u001b[38;5;179m_____________ ') # nopep8 19 | print(u' \u001b[38;5;179m/ \u001b[38;5;226m___ \u001b[38;5;179m\ \u001b[0m') # nopep8 20 | print(u' \u001b[38;5;179m|| \u001b[38;5;226m\__\\\u001b[0m \u001b[38;5;179m||\u001b[38;5;208m _ _ _ ') # nopep8 21 | print(u' \u001b[38;5;179m|| \u001b[38;5;94m_\u001b[38;5;179m ||\u001b[38;5;208m | | | | | | ') # nopep8 22 | print(u' \u001b[38;5;179m|\ \u001b[38;5;94m/ \\\u001b[38;5;179m /|\u001b[38;5;208m __ _ _ _| |_ ___ ___| |_ __ _ ___| | __') # nopep8 23 | print(u' \u001b[38;5;179m\ \___\u001b[38;5;94m/ ^ \\\u001b[38;5;179m___/ /\u001b[38;5;208m / _` | | | | __/ _ \/ __| __/ _` |/ __| |/ /') # nopep8 24 | print(u' \u001b[38;5;179m\\\\____\u001b[38;5;94m/_^_\\\u001b[38;5;179m____//\u001b[38;5;94m_\u001b[38;5;208m | (_| | |_| | || (_) \__ \ || (_| | (__| < ') # nopep8 25 | print(u' \u001b[38;5;94m__\u001b[38;5;179m\\\\____\u001b[38;5;94m/_^_\\\u001b[38;5;179m____// \u001b[38;5;94m\\\u001b[38;5;208m \__,_|\__,_|\__\___/|___/\__\__,_|\___|_|\_\\') # nopep8 26 | print(u' \u001b[38;5;94m/ \u001b[38;5;179m\____\u001b[38;5;94m/_^_\\\u001b[38;5;179m____/ \u001b[38;5;224m\ \u001b[38;5;94m\\\u001b[0m \033[1mAUTOMATING THE INEVITABLE...') # nopep8 27 | print(u' \u001b[38;5;94m/\u001b[38;5;224m/\u001b[38;5;94m \u001b[38;5;224m, \u001b[38;5;94m/ ') # nopep8 28 | print(u' \u001b[38;5;94m\\\u001b[38;5;224m\\\u001b[38;5;94m___________ \u001b[38;5;224m____ \u001b[38;5;94m/ ') # nopep8 29 | print(u' \u001b[38;5;94m\_______/\u001b[0m \n') # nopep8 30 | -------------------------------------------------------------------------------- /autostack/cli/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers 3 | Emails: elijahsawyers@gmail.com 4 | Date: 12/05/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | 8 | import click 9 | 10 | from autostack.cli.capture import capture 11 | # from autostack.cli.config import config 12 | from autostack.cli.display import display 13 | # from autostack.cli.error import error 14 | # from autostack.cli.init import init 15 | 16 | 17 | @click.group() 18 | def cli(): 19 | # pylint: disable=missing-function-docstring 20 | pass 21 | 22 | 23 | cli.add_command(capture) 24 | # cli.add_command(config) 25 | cli.add_command(display) 26 | # cli.add_command(error) 27 | # cli.add_command(init) 28 | -------------------------------------------------------------------------------- /autostack/cli/capture.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers 3 | Emails: elijahsawyers@gmail.com 4 | Date: 12/05/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | 8 | import subprocess 9 | import sys 10 | 11 | import click 12 | 13 | from autostack.cli.constants import ( 14 | PIPE_PATH 15 | ) 16 | from autostack.pipe import ( 17 | create_pipe 18 | ) 19 | 20 | 21 | @click.command() 22 | def capture(): 23 | ''' 24 | Capture all error messages outputed in the terminal for configured 25 | languages. 26 | ''' 27 | 28 | create_pipe(PIPE_PATH) 29 | 30 | if sys.platform.startswith('darwin'): # Mac 31 | subprocess.run(['script', '-q', '-F', '/tmp/monitorPipe'], check=True) 32 | else: 33 | subprocess.run(['script', '-q', '-f', '/tmp/monitorPipe'], check=True) 34 | -------------------------------------------------------------------------------- /autostack/cli/config.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers 3 | Emails: elijahsawyers@gmail.com 4 | Date: 12/05/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | 8 | import click 9 | 10 | 11 | @click.command() 12 | @click.option( 13 | '--key', 14 | required=True, 15 | help='The configuration option to change.' 16 | ) 17 | def config(): 18 | ''' 19 | Set global autostack configuration options. 20 | ''' 21 | 22 | return 23 | -------------------------------------------------------------------------------- /autostack/cli/constants.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers 3 | Emails: elijahsawyers@gmail.com 4 | Date: 12/05/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | 8 | PIPE_PATH = '/tmp/monitorPipe' 9 | -------------------------------------------------------------------------------- /autostack/cli/display.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers 3 | Emails: elijahsawyers@gmail.com 4 | Date: 12/05/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | import os 8 | 9 | import click 10 | 11 | from autostack.cli.constants import ( 12 | PIPE_PATH 13 | ) 14 | from autostack.error import ( 15 | listen_for_errors 16 | ) 17 | 18 | 19 | @click.command() 20 | def display(): 21 | ''' 22 | Display posts for all error messages captured with the 'capture' command. 23 | ''' 24 | 25 | if not os.path.exists(PIPE_PATH): 26 | print('Execute "autostack capture" in another terminal window first.') 27 | return 28 | 29 | with open(PIPE_PATH) as pipe: 30 | listen_for_errors(pipe) 31 | -------------------------------------------------------------------------------- /autostack/cli/error.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers 3 | Emails: elijahsawyers@gmail.com 4 | Date: 12/05/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | 8 | import click 9 | 10 | 11 | @click.command() 12 | @click.argument('message') 13 | def error(): 14 | ''' 15 | Query for a given error message, and dislay posts for that query. 16 | ''' 17 | 18 | return 19 | -------------------------------------------------------------------------------- /autostack/cli/init.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers 3 | Emails: elijahsawyers@gmail.com 4 | Date: 12/05/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | 8 | import json 9 | 10 | import click 11 | import regex 12 | from PyInquirer import ( 13 | prompt, 14 | Validator, 15 | ValidationError 16 | ) 17 | 18 | from autostack import ( 19 | print_logo 20 | ) 21 | 22 | 23 | @click.command() 24 | def init(): 25 | ''' 26 | Initialize a project with a .autostack.json configuration file. 27 | ''' 28 | 29 | class MaxCommentsValidator(Validator): 30 | # pylint: disable=too-few-public-methods 31 | ''' 32 | Validator for the max_comments prompt. 33 | ''' 34 | 35 | def validate(self, document): 36 | ''' 37 | Ensures that the max_comments input contains a positive integer. 38 | ''' 39 | 40 | valid = regex.match(r'^\d+$', document.text) 41 | if not valid: 42 | raise ValidationError( 43 | message='Please enter a valid integer', 44 | cursor_position=len(document.text)) 45 | 46 | questions = [ 47 | { 48 | 'type': 'checkbox', 49 | 'name': 'languages', 50 | 'message': 51 | 'What languages do you want autostack \ 52 | to capture errors for?', 53 | 'choices': [ 54 | { 55 | 'name': 'Python', 56 | 'checked': True, 57 | }, 58 | ] 59 | }, 60 | { 61 | 'type': 'checkbox', 62 | 'name': 'communities', 63 | 'message': 64 | 'Which Stack Exchange communities do you want to \ 65 | query?', 66 | 'choices': [ 67 | { 68 | 'name': 'Stack Overflow', 69 | 'checked': True, 70 | }, 71 | ] 72 | }, 73 | { 74 | 'type': 'list', 75 | 'name': 'order_by', 76 | 'message': 'How do you want to order posts?', 77 | 'choices': [ 78 | 'Relevance', 79 | 'Newest', 80 | 'Active', 81 | 'Votes', 82 | ] 83 | }, 84 | { 85 | 'type': 'confirm', 86 | 'name': 'verified_only', 87 | 'message': 88 | 'Do you want to only display posts with verified \ 89 | answers?', 90 | }, 91 | { 92 | 'type': 'confirm', 93 | 'name': 'display_comments', 94 | 'message': 95 | 'Do you want to display comments with questions and \ 96 | answers?', 97 | }, 98 | ] 99 | 100 | max_comments_questions = [ 101 | { 102 | 'type': 'input', 103 | 'name': 'max_comments', 104 | 'message': 105 | 'What\'s the max number of comments to display per \ 106 | question or answer?', 107 | 'validate': MaxCommentsValidator, 108 | } 109 | ] 110 | 111 | print_logo() 112 | answers = prompt(questions) 113 | if answers['display_comments']: 114 | answers['max_comments'] = prompt( 115 | max_comments_questions 116 | )['max_comments'] 117 | 118 | with open('./.autostack.json', 'w') as autostack_json: 119 | json.dump(answers, autostack_json, indent=4) 120 | -------------------------------------------------------------------------------- /autostack/error/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/09/2019 5 | Overview: TODO: Write overview. 6 | ''' 7 | 8 | from __future__ import absolute_import, division, print_function 9 | 10 | from autostack import print_logo 11 | from autostack.so_web_scraper import ( 12 | accepted_posts, 13 | print_accepted_post 14 | ) 15 | 16 | SYNTAX_ERRORS = [ 17 | 'SyntaxError', 18 | 'IndentationError', 19 | 'TabError', 20 | ] 21 | 22 | 23 | def listen_for_errors(pipe): 24 | ''' 25 | Reads output from a pipe until EOF, indicated by empty string. The 26 | output is parsed for errors. 27 | 28 | Parameter {File}: the pipe to read output from. 29 | ''' 30 | 31 | print_logo() 32 | print_listening_for_errors() 33 | 34 | while True: 35 | output = pipe.readline() 36 | 37 | # Pipe closed. 38 | if output == '': 39 | break 40 | 41 | parse_output_for_error(output, pipe) 42 | 43 | 44 | def parse_output_for_error(output, pipe): 45 | ''' 46 | Parses a line of output, and determines whether or not it is 47 | an error message. There are two types of errors, syntax errors 48 | and runtime errors. Syntax errors do not have a traceback but 49 | runtime errors do. 50 | 51 | e.g. without traceback: 52 | IndentationError: unexpected indent 53 | e.g. with traceback: 54 | Traceback (most recent call last): 55 | File "", line 1, in 56 | NameError: name 'xyz' is not defined 57 | 58 | Parameter {str} output: line of output from a pipe. 59 | Parameter {File} pipe: pipe to read output from, in case of traceback. 60 | ''' 61 | 62 | try: 63 | # Syntax errors - no traceback. 64 | if output.split()[0][:-1] in SYNTAX_ERRORS: 65 | error = output.split()[0][:-1] 66 | handle_exception(error) 67 | # Runtime error - has traceback. 68 | elif 'Traceback' in output.split(): 69 | error = get_error_from_traceback(pipe) 70 | handle_exception(error) 71 | except IndexError: 72 | pass 73 | 74 | 75 | def get_error_from_traceback(pipe): 76 | ''' 77 | Gets the error description from a traceback. 78 | 79 | e.g.: 80 | Traceback (most recent call last): 81 | File "", line 1, in 82 | NameError: name 'xyz' is not defined 83 | would return 'NameError'. 84 | 85 | Parameter {File} pipe: the pipe to read the traceback from. 86 | Returns {str}: the error description. 87 | ''' 88 | 89 | output = pipe.readline() 90 | 91 | while ( 92 | output.split()[0][-1] != ':' 93 | ): 94 | output = pipe.readline() 95 | 96 | return output.split()[0][:-1] 97 | 98 | 99 | def handle_exception(query): 100 | ''' 101 | When passed a query, this function loops over each accepted 102 | Stack Overflow post, and displays them, until the user inputs 103 | 'Y'. 104 | 105 | Parameter {str} query: the query to display posts for. 106 | ''' 107 | 108 | for post in accepted_posts(query): 109 | # Display Stack Overflow posts for the error. 110 | clear_terminal() 111 | print_accepted_post(post) 112 | 113 | user_input = handle_user_input() 114 | 115 | # Custom query. 116 | if user_input not in (True, False): 117 | handle_exception(user_input) 118 | return 119 | 120 | # Error solved, break out of the loop. 121 | if user_input is True: 122 | clear_terminal() 123 | print_listening_for_errors() 124 | return 125 | 126 | # Otherwise, the question wasn't answered, keep looping. 127 | 128 | 129 | def handle_user_input(): 130 | ''' 131 | Prompts the user to input whether or not his/her error was solved. 132 | Valid inputs are 'Y' and 'n'. 'Y' meaning the error was solved and 133 | 'f' meaning it wasn't solved. Otherwise, whatever the user entered 134 | is used for a custom query. 135 | 136 | Returns: True if 'Y' or False if 'f' was inputed; otherwise, returns 137 | the raw user input (custom query). 138 | ''' 139 | 140 | user_input = input('Did this solve your error? (Y/n or custom query): ') 141 | 142 | if user_input not in ('Y', 'n'): 143 | return user_input 144 | 145 | if user_input == 'Y': 146 | return True 147 | 148 | return False 149 | 150 | 151 | def print_listening_for_errors(): 152 | ''' 153 | Prints "🥞 Listening for Python errors..." 154 | ''' 155 | 156 | print(u'\U0001F95E Listening for Python errors...') 157 | 158 | 159 | def clear_terminal(): 160 | ''' 161 | Clears the terminal window. 162 | ''' 163 | 164 | print(u'\033c') 165 | -------------------------------------------------------------------------------- /autostack/error/__tests__/mock_handle_exception.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/23/2019 5 | Overview: Mocks the handle_exception function. 6 | ''' 7 | 8 | 9 | class MockHandleException: 10 | ''' 11 | Mocks the handle_exception function. 12 | ''' 13 | 14 | def __init__(self): 15 | ''' 16 | Initializes a mock handle_exception with was_called set 17 | to False and parameter set to None. 18 | ''' 19 | 20 | self.was_called = False 21 | self.parameter = None 22 | 23 | def handle_exception(self, error): 24 | ''' 25 | Sets was_called to True, indicating that the function was 26 | called, and parameter is set to the error string passed into 27 | the function. 28 | ''' 29 | 30 | self.was_called = True 31 | self.parameter = error 32 | -------------------------------------------------------------------------------- /autostack/error/__tests__/mock_pipe.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/23/2019 5 | Overview: Mocks a pipe. 6 | ''' 7 | 8 | 9 | class MockPipe: 10 | ''' 11 | Mocks a pipe. 12 | ''' 13 | 14 | def __init__(self, readline_returns=''): 15 | ''' 16 | Initializes a mock pipe with readline return values and 17 | the number of readline calls set to 0. 18 | ''' 19 | 20 | self.readline_returns = readline_returns 21 | self.readline_call_count = 0 22 | 23 | def readline(self): 24 | ''' 25 | Returns a value for readline, based on the current call 26 | count value. 27 | ''' 28 | 29 | readline_val = self.readline_returns[self.readline_call_count] 30 | self.readline_call_count += 1 31 | return readline_val 32 | 33 | def get_readline_call_count(self): 34 | ''' 35 | Returns the readline method call count. 36 | ''' 37 | 38 | return self.readline_call_count 39 | -------------------------------------------------------------------------------- /autostack/error/__tests__/test_error.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/22/2019 5 | Overview: Tests for the error package. 6 | ''' 7 | 8 | from autostack.error import ( 9 | listen_for_errors, 10 | parse_output_for_error, 11 | get_error_from_traceback, 12 | handle_exception, 13 | handle_user_input, 14 | print_listening_for_errors, 15 | clear_terminal, 16 | ) 17 | from autostack.error.__tests__.mock_pipe import MockPipe 18 | from autostack.error.__tests__.mock_handle_exception import MockHandleException 19 | 20 | 21 | def test_listen_for_errors(monkeypatch): 22 | ''' 23 | Ensures that listen_for_errors reads output from a pipe until 24 | empty string is returned. In this case, that'd be 3 calls. 25 | ''' 26 | 27 | # 1. Given. 28 | def mock_print(): 29 | ''' 30 | Mocks the print_logo and print_listening_for_errors function. 31 | ''' 32 | 33 | return 34 | 35 | def mock_parse_output_for_error(output, pipe): 36 | # pylint: disable=unused-argument 37 | ''' 38 | Mocks the parse_output_for_error function. 39 | ''' 40 | 41 | return 42 | 43 | monkeypatch.setattr( 44 | 'autostack.error.print_logo', 45 | mock_print 46 | ) 47 | 48 | monkeypatch.setattr( 49 | 'autostack.error.print_listening_for_errors', 50 | mock_print 51 | ) 52 | 53 | monkeypatch.setattr( 54 | 'autostack.error.parse_output_for_error', 55 | mock_parse_output_for_error 56 | ) 57 | 58 | mockpipe = MockPipe(['output', 'output', '']) 59 | 60 | # 2. When. 61 | listen_for_errors(mockpipe) 62 | 63 | # 3. Then. 64 | assert mockpipe.get_readline_call_count() == 3 65 | 66 | 67 | def test_parse_output_for_error_non_error(monkeypatch): 68 | ''' 69 | Ensures that handle_exception is never called when a non-error 70 | is passed into parse_output_for_error. 71 | ''' 72 | 73 | # 1. Given. 74 | mock_handle_exception = MockHandleException() 75 | 76 | monkeypatch.setattr( 77 | 'autostack.error.handle_exception', 78 | mock_handle_exception.handle_exception 79 | ) 80 | 81 | output = 'Not an error.' 82 | 83 | # 2. When. 84 | parse_output_for_error(output, None) 85 | 86 | # 3. Then. 87 | assert not mock_handle_exception.parameter 88 | assert not mock_handle_exception.was_called 89 | 90 | 91 | def test_parse_output_for_error_with_error(monkeypatch): 92 | ''' 93 | Ensures that handle_exception is called when an error 94 | is passed into parse_output_for_error. 95 | ''' 96 | 97 | # 1. Given. 98 | mock_handle_exception = MockHandleException() 99 | 100 | monkeypatch.setattr( 101 | 'autostack.error.handle_exception', 102 | mock_handle_exception.handle_exception 103 | ) 104 | 105 | output = 'IndentationError: unexpected indent' 106 | 107 | # 2. When. 108 | parse_output_for_error(output, None) 109 | 110 | # 3. Then. 111 | assert mock_handle_exception.parameter == 'IndentationError' 112 | assert mock_handle_exception.was_called 113 | 114 | 115 | def test_parse_output_for_error_traceback(monkeypatch): 116 | ''' 117 | Ensures that handle_exception is called when a traceback 118 | is passed into parse_output_for_error. 119 | ''' 120 | 121 | # 1. Given. 122 | mock_pipe = MockPipe([ 123 | ' File "", line 1, in ', 124 | 'NameError: name \'xyz\' is not defined' 125 | ]) 126 | mock_handle_exception = MockHandleException() 127 | output = 'Traceback (most recent call last):' 128 | 129 | def mock_get_error_from_traceback(pipe): 130 | # pylint: disable=unused-argument 131 | ''' 132 | Mocks the get_error_from_traceback function. 133 | ''' 134 | 135 | return 'NameError' 136 | 137 | monkeypatch.setattr( 138 | 'autostack.error.handle_exception', 139 | mock_handle_exception.handle_exception 140 | ) 141 | 142 | monkeypatch.setattr( 143 | 'autostack.error.get_error_from_traceback', 144 | mock_get_error_from_traceback 145 | ) 146 | 147 | # 2. When. 148 | parse_output_for_error(output, mock_pipe) 149 | 150 | # 3. Then. 151 | assert mock_handle_exception.parameter == 'NameError' 152 | assert mock_handle_exception.was_called 153 | 154 | 155 | def test_parse_output_for_error_index_error(monkeypatch): 156 | ''' 157 | Ensures that parse_output_for_error catches index errors. 158 | ''' 159 | 160 | # 1. Given. 161 | mock_handle_exception = MockHandleException() 162 | 163 | monkeypatch.setattr( 164 | 'autostack.error.handle_exception', 165 | mock_handle_exception.handle_exception 166 | ) 167 | 168 | output = '' 169 | 170 | # 2. When. 171 | parse_output_for_error(output, None) 172 | 173 | # 3. Then. 174 | assert not mock_handle_exception.parameter 175 | assert not mock_handle_exception.was_called 176 | 177 | 178 | def test_get_error_from_traceback(): 179 | ''' 180 | Ensures that the error description is returned from a 181 | traceback. 182 | ''' 183 | 184 | # 1. Given. 185 | mock_pipe = MockPipe([ 186 | ' File "", line 1, in ', 187 | 'NameError: name \'xyz\' is not defined' 188 | ]) 189 | 190 | # 2. When. 191 | error = get_error_from_traceback(mock_pipe) 192 | 193 | # 3. Then. 194 | assert error == 'NameError' 195 | assert mock_pipe.get_readline_call_count() == 2 196 | 197 | 198 | def test_handle_exception(capsys, monkeypatch): 199 | ''' 200 | Ensures that posts are printed until the user inputs 'Y' 201 | to stop. 202 | ''' 203 | 204 | # 1. Given. 205 | def mock_accepted_posts(*args): 206 | # pylint: disable=unused-argument 207 | ''' 208 | Mocks the accepted_posts function which yields 209 | strings instead of actual post bs4 soup. 210 | ''' 211 | 212 | i = 1 213 | 214 | while i: 215 | yield str(i) 216 | 217 | def mock_print_accepted_post(*args): 218 | # pylint: disable=unused-argument 219 | ''' 220 | Mocks the print_accepted_post function. 221 | ''' 222 | 223 | return 224 | 225 | def make_mock_handle_user_input(): 226 | ''' 227 | Creates the mock function to mock handle_user_input, and the 228 | input is different based on the call count. 229 | ''' 230 | 231 | call_count = 0 232 | 233 | def mock_handle_user_input(): 234 | ''' 235 | Mocks the handle_user_input function. 236 | ''' 237 | 238 | nonlocal call_count 239 | call_count += 1 240 | 241 | if call_count == 1: 242 | return False 243 | 244 | if call_count == 2: 245 | return 'Custom query' 246 | 247 | return True 248 | 249 | return mock_handle_user_input 250 | 251 | def mock_clear_terminal(): 252 | ''' 253 | Mocks the clear_terminal function. 254 | ''' 255 | 256 | return 257 | 258 | def mock_print_listening_for_errors(): 259 | ''' 260 | Mocks the print_listening_for_errors function. 261 | ''' 262 | 263 | print(u'\U0001F95E Listening for Python errors...') 264 | 265 | monkeypatch.setattr( 266 | 'autostack.error.accepted_posts', 267 | mock_accepted_posts 268 | ) 269 | 270 | monkeypatch.setattr( 271 | 'autostack.error.print_accepted_post', 272 | mock_print_accepted_post 273 | ) 274 | 275 | monkeypatch.setattr( 276 | 'autostack.error.handle_user_input', 277 | make_mock_handle_user_input() 278 | ) 279 | 280 | monkeypatch.setattr( 281 | 'autostack.error.clear_terminal', 282 | mock_clear_terminal 283 | ) 284 | 285 | monkeypatch.setattr( 286 | 'autostack.error.print_listening_for_errors', 287 | mock_print_listening_for_errors 288 | ) 289 | 290 | # 2. When. 291 | handle_exception('Error') 292 | 293 | # 3. Then. 294 | captured = capsys.readouterr() 295 | assert captured.out == u'\U0001F95E Listening for Python errors...\n' 296 | 297 | 298 | def test_handle_user_input_y(monkeypatch): 299 | ''' 300 | Ensures that when 'Y' is inputted, handle_user_input returns True. 301 | ''' 302 | 303 | # 1. Given. 304 | def mock_input(*args): 305 | # pylint: disable=unused-argument 306 | ''' 307 | Mocks user input to be 'Y'. 308 | ''' 309 | 310 | return 'Y' 311 | 312 | monkeypatch.setattr('builtins.input', mock_input) 313 | 314 | # 2. When. 315 | user_input = handle_user_input() 316 | 317 | # 3. Then. 318 | assert user_input is True 319 | 320 | 321 | def test_handle_user_input_n(monkeypatch): 322 | ''' 323 | Ensures that when 'n' is inputted, handle_user_input returns False. 324 | ''' 325 | 326 | # 1. Given. 327 | def mock_input(*args): 328 | # pylint: disable=unused-argument 329 | ''' 330 | Mocks user input to be 'n'. 331 | ''' 332 | 333 | return 'n' 334 | 335 | monkeypatch.setattr('builtins.input', mock_input) 336 | 337 | # 2. When. 338 | user_input = handle_user_input() 339 | 340 | # 3. Then. 341 | assert not user_input 342 | 343 | 344 | def test_handle_user_input_custom_query(monkeypatch): 345 | ''' 346 | Ensures that when input isn't Y/n, handle_user_input returns the input. 347 | ''' 348 | 349 | # 1. Given. 350 | def make_mock_input(): 351 | ''' 352 | Creates the mock function to mock user input, and the 353 | input is different based on the call count. 354 | ''' 355 | 356 | call_count = 0 357 | 358 | def mock_input(*args): 359 | # pylint: disable=unused-argument 360 | ''' 361 | Mocks user input to be 'a' then 'Y'. 362 | ''' 363 | 364 | nonlocal call_count 365 | if call_count == 0: 366 | call_count += 1 367 | return 'Custom query' 368 | return 'Y' 369 | return mock_input 370 | 371 | monkeypatch.setattr('builtins.input', make_mock_input()) 372 | 373 | # 2. When. 374 | user_input = handle_user_input() 375 | 376 | # 3. Then. 377 | assert user_input == 'Custom query' 378 | 379 | 380 | def test_print_listening_for_errors(capsys): 381 | ''' 382 | Ensures that print_listening_for_errors prints the proper output. 383 | 384 | "🥞 Listening for Python errors..." 385 | ''' 386 | 387 | # 1. Given. 388 | 389 | # 2. When. 390 | print_listening_for_errors() 391 | 392 | # 3. Then. 393 | captured = capsys.readouterr() 394 | assert captured.out == u'\U0001F95E Listening for Python errors...\n' 395 | 396 | 397 | def test_clear_terminal(capsys): 398 | ''' 399 | Ensures that clear_terminal clears the terminal. 400 | ''' 401 | 402 | # 1. Given. 403 | 404 | # 2. When. 405 | clear_terminal() 406 | 407 | # 3. Then. 408 | captured = capsys.readouterr() 409 | assert captured.out == '\x1bc\n' # Same as u'\033c' 410 | -------------------------------------------------------------------------------- /autostack/main.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 03/17/2019 5 | Overview: Entry point of autostack. 6 | ''' 7 | 8 | from autostack.cli import cli 9 | 10 | 11 | def main(): 12 | ''' 13 | Entry point of autostack. 14 | ''' 15 | 16 | cli() 17 | -------------------------------------------------------------------------------- /autostack/pipe/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/09/2019 5 | Overview: 6 | ''' 7 | 8 | import os 9 | 10 | 11 | def create_pipe(path): 12 | ''' 13 | TODO: Write docstring. 14 | 15 | Parameter {string}: the full path to the fifo. 16 | Returns: The pipe in read mode. 17 | ''' 18 | 19 | leaf_dir_path = '/'.join(path.split('/')[:-1]) 20 | 21 | if not os.path.exists(leaf_dir_path): 22 | os.makedirs(leaf_dir_path) 23 | 24 | try: 25 | os.mkfifo(path) 26 | except (FileExistsError, OSError): 27 | pass 28 | -------------------------------------------------------------------------------- /autostack/pipe/__tests__/test_pipe.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/22/2019 5 | Overview: Tests for the pipe package. 6 | ''' 7 | 8 | import shutil 9 | import os 10 | 11 | from autostack.pipe import create_pipe 12 | 13 | 14 | def test_create_pipe_dir_doesnt_exist(): 15 | '''' 16 | Ensures that create_pipe creates the directories to the pipe 17 | recursively. 18 | ''' 19 | 20 | # 1. Given. 21 | path = '/tmp/test/dir/pipe' 22 | 23 | # 2. When. 24 | create_pipe(path) 25 | 26 | # 3. Then. 27 | assert os.path.exists(path) 28 | 29 | shutil.rmtree('/tmp/test/') 30 | 31 | 32 | def test_create_pipe_file_doesnt_exist(): 33 | '''' 34 | Ensures that create_pipe creates a pipe, if it doesn't exist. 35 | ''' 36 | 37 | # 1. Given. 38 | path = '/tmp/pipe' 39 | 40 | # 2. When. 41 | create_pipe(path) 42 | 43 | # 3. Then. 44 | assert os.path.exists(path) 45 | 46 | os.remove(path) 47 | 48 | 49 | def test_create_pipe_file_already_exists(): 50 | '''' 51 | Ensures that if a file already exists at the specifies path, 52 | it will not be overwritten. 53 | ''' 54 | 55 | # 1. Given. 56 | path = '/tmp/pipe' 57 | 58 | # 2. When. 59 | os.mkfifo(path) 60 | mtime = os.path.getmtime(path) 61 | create_pipe(path) 62 | 63 | # 3. Then. 64 | assert os.path.getmtime(path) == mtime 65 | -------------------------------------------------------------------------------- /autostack/so_web_scraper/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 03/17/2019 5 | Overview: Contains the StackOverflowScraper class which is used to 6 | scrape Stack Overflow for posts with accepted answers for a given query. 7 | ''' 8 | 9 | from __future__ import absolute_import, division, print_function 10 | from bs4 import BeautifulSoup 11 | import pygments 12 | from pygments.lexers import PythonLexer # pylint: disable=no-name-in-module 13 | import requests 14 | from termcolor import colored 15 | 16 | BASE_URL = 'https://stackoverflow.com' 17 | 18 | 19 | def accepted_posts(query): 20 | ''' 21 | A generator that queries Stack Overflow and yields posts with 22 | accepted answers. 23 | 24 | Parameter {str} query: the string to query Stack Overflow with. 25 | Yields {bs4.BeautifulSoup}: accepted posts html documents. 26 | ''' 27 | 28 | for result_set in get_post_summaries(query): 29 | for post_summary in result_set: 30 | post = post_soup(post_summary) 31 | 32 | if post: 33 | yield post 34 | 35 | 36 | def get_post_summaries(query): 37 | ''' 38 | A generator that queries Stack Overflow and yields a ResultSet 39 | of post summaries. 40 | 41 | Parameter {str} query: the string to query Stack Overflow with. 42 | Yields {bs4.element.ResultSet}: ResultSet of post summaries. 43 | ''' 44 | 45 | page = 1 46 | 47 | while True: 48 | query_url = build_query_url(query, page) 49 | query_soup = query_stack_overflow(query_url) 50 | 51 | if not query_soup: 52 | break 53 | 54 | post_summaries = query_soup.find_all( 55 | attrs={ 56 | 'class': 'question-summary' 57 | } 58 | ) 59 | 60 | if not post_summaries: 61 | break 62 | 63 | yield post_summaries 64 | 65 | page += 1 66 | 67 | 68 | def build_query_url(query, page): 69 | ''' 70 | Builds a URL to query Stack Overflow with. 71 | 72 | e.g. query == 'Test Query' and page == 1 then the url will be: 73 | https://stackoverflow.com/search?page=1&tab=Relevance&q=%5Bpython%5D+Test+Query 74 | 75 | Parameter {str} query: the string to query Stack Overflow with. 76 | Parameter {int} page: the page to select in the query. 77 | ''' 78 | 79 | query_url = '{}/search?page={}&tab=Relevance&q=%5Bpython%5D'.format( 80 | BASE_URL, 81 | page 82 | ) 83 | 84 | for query_string in query.split(' '): 85 | query_url = '{}+{}'.format(query_url, query_string) 86 | 87 | return query_url 88 | 89 | 90 | def query_stack_overflow(url): 91 | ''' 92 | Given a url, this function returns the BeautifulSoup of the 93 | request. 94 | 95 | Parameter {str} url: the url to request. 96 | Returns {bs4.BeautifulSoup}: the BeautifulSoup of the request. 97 | ''' 98 | 99 | try: 100 | response = requests.get(url) 101 | response.raise_for_status() 102 | except requests.exceptions.HTTPError: 103 | return None 104 | 105 | return BeautifulSoup(response.text, 'lxml') 106 | 107 | 108 | def post_soup(post_summary): 109 | ''' 110 | Given a post summary, query Stack Overflow, and return the 111 | BeautifulSoup of the post, if it has an accepted answer. 112 | 113 | Parameter {bs4.Tag} post_summary: the bs4.Tag post summary. 114 | Parameter {bs4.BeautifulSoup}: the BeautifulSoup of the post, 115 | if it has an accepted answer; otherwise, None. 116 | ''' 117 | 118 | if has_accepted_answer(post_summary): 119 | post_url = get_post_url(post_summary) 120 | 121 | try: 122 | response = requests.get(BASE_URL + post_url) 123 | response.raise_for_status() 124 | except requests.exceptions.HTTPError: 125 | return None 126 | 127 | return BeautifulSoup(response.text, 'lxml') 128 | 129 | return None 130 | 131 | 132 | def has_accepted_answer(post_summary): 133 | ''' 134 | Given a post summary, this function determines whether or not 135 | the post has an accepted answer. 136 | 137 | Parameter {bs4.Tag} post_summary: the post summary. 138 | Returns {Boolean}: True if the post has an accepted answer; 139 | otherwise, False. 140 | ''' 141 | 142 | accepted_answer = post_summary.find( 143 | attrs={ 144 | 'class': 'answered-accepted' 145 | } 146 | ) 147 | 148 | if not accepted_answer: 149 | return False 150 | 151 | return True 152 | 153 | 154 | def get_post_url(post_summary): 155 | ''' 156 | Given a post summary, this function returns the post's url. 157 | 158 | Parameter {bs4.Tag} post_summary: the post summary. 159 | Returns {str:None}: post url, or None, if the post url couldn't 160 | be found. 161 | ''' 162 | 163 | try: 164 | return post_summary.find( 165 | attrs={ 166 | 'class': 'question-hyperlink' 167 | }, 168 | href=True 169 | )['href'] 170 | except KeyError: 171 | return None 172 | 173 | 174 | def print_accepted_post(post): 175 | ''' 176 | Prints a Stack Overflow post with an accepted answer. 177 | 178 | Parameter {bs4.BeautifulSoup} post: The 'soup' of the post 179 | to print. 180 | ''' 181 | 182 | question = get_post_text(post, 'question') 183 | accepted_answer = get_post_text(post, 'accepted-answer') 184 | 185 | if question is None or accepted_answer is None: 186 | return 187 | 188 | print(colored('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', 'red')) 189 | print(colored('Question:', 'red')) 190 | 191 | # Print the question. 192 | print_post_text(question) 193 | 194 | print(colored('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', 'red')) 195 | print(colored('Answer:', 'red')) 196 | 197 | # Print the answer. 198 | print_post_text(accepted_answer) 199 | 200 | print(colored('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', 'red')) 201 | 202 | 203 | def get_post_text(post, html_class): 204 | ''' 205 | Given a post, and a html class, this function returns a 206 | bs4.Tag with the post-text. 207 | Typically, you'd only pass 'question' or 'accepted-answer' as 208 | the html class. 209 | 210 | Parameter {bs4.BeautifulSoup} post: the post to get post-text from. 211 | Parameter {str} html_class: the html class of the elementto get 212 | post-text from. 213 | Returns {bs4.Tag}: the post-text. 214 | ''' 215 | 216 | try: 217 | return post.find( 218 | attrs={ 219 | 'class', 220 | html_class 221 | } 222 | ).find( 223 | attrs={ 224 | 'class', 225 | 'post-text' 226 | } 227 | ) 228 | except AttributeError: 229 | return None 230 | 231 | 232 | def print_post_text(post_text): 233 | ''' 234 | Prints post-text from Stack Overflow. 235 | 236 | On Stack Overflow, a div with a class of 'post-text' 237 | indicates that the div is either a question or an answer. 238 | 239 | Different elements of the post-text are printed in different 240 | colors. 241 | 242 | Headers: White. 243 | Text: White. 244 | Quotes: Yellow. 245 | Lists: Syntax Highlighted in print_ul. 246 | Code: Syntax Highlighted in print_code_block. 247 | 248 | Parameter {bs4.Tag} post_text: HTML 'div' element from a Stack Overflow 249 | post with class of 'post-text.' 250 | ''' 251 | 252 | element_colors = { 253 | 'h1': 'white', 254 | 'h2': 'white', 255 | 'h3': 'white', 256 | 'p': 'white', 257 | 'blockquote': 'yellow', 258 | } 259 | 260 | for element in post_text: 261 | if element.name in element_colors.keys(): 262 | print( 263 | colored(element.text, element_colors[element.name]) 264 | ) 265 | elif element.name == 'ul': # Lists. 266 | print_ul(element) 267 | elif element.name == 'pre': # Code. 268 | print_code_block(element.find('code')) 269 | 270 | 271 | def print_ul(ul_element): 272 | ''' 273 | Prints an unordered list. 274 | 275 | Parameter {bs4.Tag} ul_element: the unordered list to print. 276 | ''' 277 | 278 | for item in ul_element.find_all('li'): 279 | print( 280 | colored(' - ' + item.text, 'green', attrs=['bold']) 281 | ) 282 | 283 | 284 | def print_code_block(code_block): 285 | ''' 286 | Prints a code block from Stack Overflow with syntax highlighting. 287 | 288 | On Stack Overflow, the code in a HTML 'code' element contains 289 | a 'span' element for each token. Because of this, it's necessary 290 | to grab each of the 'code' element's 'span' elements' values to get 291 | the actual code. 292 | 293 | Parameter {bs4.Tag} code_block: 'soup' of a HTML 294 | 'code' element from a Stack Overflow post. 295 | ''' 296 | 297 | token_colors = { 298 | 'Token.Keyword': 'blue', 299 | 'Token.Name.Builtin.Pseudo': 'blue', 300 | 'Token.Literal.Number.Integer': 'green', 301 | 'Token.Literal.Number.Float': 'green', 302 | 'Token.Comment.Single': 'green', 303 | 'Token.Comment.Hashbang': 'green', 304 | 'Token.Literal.String.Single': 'yellow', 305 | 'Token.Literal.String.Double': 'yellow', 306 | 'Token.Literal.String.Doc': 'yellow' 307 | } 308 | 309 | print('') 310 | 311 | # Store the code's text. 312 | code = get_src_code(code_block) 313 | 314 | # Loop over code, and highlight. 315 | for token, content in pygments.lex(code, PythonLexer()): 316 | try: 317 | print( 318 | colored(content, token_colors[str(token)]), 319 | end='' 320 | ) 321 | except KeyError: 322 | print( 323 | content, 324 | end='' 325 | ) 326 | 327 | print('') 328 | 329 | 330 | def get_src_code(code_block): 331 | ''' 332 | Loops over a code block and grabs the 'source code' 333 | (i.e. text). 334 | 335 | Parameter {bs4.Tag} code_block: the source code (or text). 336 | Returns {str}: the source code (or text). 337 | ''' 338 | 339 | code = '' 340 | 341 | # Loop through code spans. 342 | for token in code_block: 343 | try: # bs4.NavigableString 344 | code += token 345 | except TypeError: # bs4.Tag 346 | code += get_src_code(token.contents) 347 | 348 | return code 349 | -------------------------------------------------------------------------------- /autostack/so_web_scraper/__tests__/data/post_text.html: -------------------------------------------------------------------------------- 1 |
2 |

Test 1

3 |

Test 2

4 |

Test 3

5 |

Test 4

6 |
Test 5
7 |
    8 |
  • Test 6
  • 9 |
10 |
Test 7
11 |
-------------------------------------------------------------------------------- /autostack/so_web_scraper/__tests__/data/post_text_code.html: -------------------------------------------------------------------------------- 1 |
2 | l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] 3 | reduce(lambda x, y: x.extend(y), l) 4 |
-------------------------------------------------------------------------------- /autostack/so_web_scraper/__tests__/data/post_text_ul_empty.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
4 |
-------------------------------------------------------------------------------- /autostack/so_web_scraper/__tests__/data/post_text_ul_populated.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • Test 1
  • 4 |
  • Test 2
  • 5 |
  • Test 3
  • 6 |
7 |
-------------------------------------------------------------------------------- /autostack/so_web_scraper/__tests__/data/query_no_post_summaries.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autostack-team/autostack/a96eb87c0a210d5a0d46257d1f64445622c5ee16/autostack/so_web_scraper/__tests__/data/query_no_post_summaries.html -------------------------------------------------------------------------------- /autostack/so_web_scraper/__tests__/mock_response.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/25/2019 5 | Overview: Mocks the request package. 6 | ''' 7 | 8 | import requests 9 | 10 | 11 | class MockResponse: 12 | ''' 13 | A mock requests Response object. 14 | ''' 15 | 16 | def __init__(self, path_to_html, status): 17 | ''' 18 | Initializes a mock response with html text and a 19 | HTTP status. 20 | ''' 21 | 22 | self.text = open(path_to_html).read() 23 | self.status = status 24 | 25 | def raise_for_status(self): 26 | ''' 27 | Raises an requests.exceptions.HTTPError if the HTTP 28 | status is not within 200-399. 29 | ''' 30 | 31 | if self.status < 200 or self.status > 399: 32 | raise requests.exceptions.HTTPError 33 | 34 | def get_text(self): 35 | ''' 36 | Getter for text. 37 | ''' 38 | 39 | return self.text 40 | 41 | def get_status(self): 42 | ''' 43 | Getter for status. 44 | ''' 45 | 46 | return self.status 47 | 48 | 49 | def build_mock_get(mock_response): 50 | ''' 51 | Builds a mock requests get method. 52 | ''' 53 | 54 | def mock_get(*args): 55 | # pylint: disable=unused-argument 56 | ''' 57 | Mocks requests get method. 58 | 59 | Returns {MockResponse}: a mock response. 60 | ''' 61 | 62 | nonlocal mock_response 63 | return mock_response 64 | 65 | return mock_get 66 | -------------------------------------------------------------------------------- /autostack/so_web_scraper/__tests__/test_so_web_scraper.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Authors: Elijah Sawyers, Benjamin Sanders 3 | Emails: elijahsawyers@gmail.com, ben.sanders97@gmail.com 4 | Date: 10/25/2019 5 | Overview: Tests for the so_web_scraper package. 6 | ''' 7 | 8 | import re 9 | from bs4 import BeautifulSoup 10 | 11 | from autostack.so_web_scraper import ( 12 | accepted_posts, 13 | get_post_summaries, 14 | build_query_url, 15 | query_stack_overflow, 16 | post_soup, 17 | has_accepted_answer, 18 | get_post_url, 19 | print_accepted_post, 20 | get_post_text, 21 | print_post_text, 22 | print_ul, 23 | print_code_block, 24 | get_src_code, 25 | ) 26 | from autostack.so_web_scraper.__tests__.mock_response import ( 27 | MockResponse, 28 | build_mock_get 29 | ) 30 | 31 | ANSI_ESCAPE = re.compile(r'\x1B[@-_][0-?]*[ -/]*[@-~]') 32 | 33 | 34 | def test_accepted_posts(monkeypatch): 35 | ''' 36 | Ensures that accepted_posts loops over each post summary. 37 | ''' 38 | 39 | # 1. Given. 40 | post_soup_call_count = 0 41 | 42 | def mock_get_post_summaries(*args): 43 | # pylint: disable=unused-argument 44 | ''' 45 | Mocks the get_post_summaries function 46 | ''' 47 | 48 | html = open( 49 | 'autostack/so_web_scraper/__tests__/data/query_post_summaries.html' 50 | ).read() 51 | 52 | post_summaries = BeautifulSoup(html, 'lxml').find_all( 53 | attrs={ 54 | 'class': 'question-summary' 55 | } 56 | ) 57 | 58 | return [post_summaries] 59 | 60 | def mock_post_soup(*args): 61 | # pylint: disable=unused-argument 62 | ''' 63 | Mocks the post_soup function 64 | ''' 65 | nonlocal post_soup_call_count 66 | post_soup_call_count += 1 67 | return 'SOUP' 68 | 69 | monkeypatch.setattr( 70 | 'autostack.so_web_scraper.get_post_summaries', 71 | mock_get_post_summaries 72 | ) 73 | 74 | monkeypatch.setattr( 75 | 'autostack.so_web_scraper.post_soup', 76 | mock_post_soup 77 | ) 78 | 79 | # 2. When. 80 | # pylint: disable=unused-variable 81 | for post in accepted_posts(None): 82 | pass 83 | 84 | # 3. Then. 85 | assert post_soup_call_count == 15 86 | 87 | 88 | def test_get_post_summaries(monkeypatch): 89 | ''' 90 | Ensures that the generator yields post summaries until 91 | there aren't anymore post summaries. 92 | ''' 93 | 94 | # 1. Given. 95 | query_stack_overflow_call_count = 0 96 | 97 | def mock_build_query_url(*args): 98 | # pylint: disable=unused-argument 99 | ''' 100 | Mocks the build_query_url function. 101 | ''' 102 | 103 | return 104 | 105 | def mock_query_stack_overflow(*args): 106 | # pylint: disable=unused-argument 107 | ''' 108 | Mocks the query_stack_overflow function. 109 | ''' 110 | 111 | nonlocal query_stack_overflow_call_count 112 | query_stack_overflow_call_count += 1 113 | 114 | base = 'autostack/so_web_scraper/__tests__/data/' 115 | 116 | if query_stack_overflow_call_count == 3: 117 | return BeautifulSoup(open( 118 | base + 'query_no_post_summaries.html' 119 | ).read(), 'lxml') 120 | 121 | return BeautifulSoup(open( 122 | base + 'query_post_summaries.html' 123 | ).read(), 'lxml') 124 | 125 | monkeypatch.setattr( 126 | 'autostack.so_web_scraper.build_query_url', 127 | mock_build_query_url 128 | ) 129 | 130 | monkeypatch.setattr( 131 | 'autostack.so_web_scraper.query_stack_overflow', 132 | mock_query_stack_overflow 133 | ) 134 | 135 | # 2. When. 136 | # pylint: disable=unused-variable 137 | for post_summaries in get_post_summaries(None): 138 | pass 139 | 140 | # 3. Then. 141 | assert query_stack_overflow_call_count == 3 142 | 143 | 144 | def test_get_post_summaries_no_query_soup(monkeypatch): 145 | ''' 146 | Ensures that the generator yields nothing when there's 147 | no BeautifulSoup returned from querying Stack Overflow. 148 | ''' 149 | 150 | # 1. Given. 151 | def mock_build_query_url(*args): 152 | # pylint: disable=unused-argument 153 | ''' 154 | Mocks the build_query_url function. 155 | ''' 156 | 157 | return 158 | 159 | def mock_query_stack_overflow(*args): 160 | # pylint: disable=unused-argument 161 | ''' 162 | Mocks the query_stack_overflow function. 163 | ''' 164 | 165 | return None 166 | 167 | monkeypatch.setattr( 168 | 'autostack.so_web_scraper.build_query_url', 169 | mock_build_query_url 170 | ) 171 | 172 | monkeypatch.setattr( 173 | 'autostack.so_web_scraper.query_stack_overflow', 174 | mock_query_stack_overflow 175 | ) 176 | 177 | # 2. When. 178 | post_count = 0 179 | 180 | # pylint: disable=unused-variable 181 | for post_summaries in get_post_summaries(None): 182 | post_count += 1 183 | 184 | # 3. Then. 185 | assert post_count == 0 186 | 187 | 188 | def test_build_query_url(): 189 | ''' 190 | Ensures that the proper URL is built with build_query_url. 191 | ''' 192 | 193 | # 1. Given. 194 | base_url = 'https://stackoverflow.com' 195 | page = 1 196 | query = 'Test Query' 197 | 198 | # 2. When. 199 | url = build_query_url(query, page) 200 | 201 | # 3. Then. 202 | assert '{}/search?page={}&tab=Relevance&q=%5Bpython%5D+Test+Query'.format( 203 | base_url, 204 | page 205 | ) == url 206 | 207 | 208 | def test_query_stack_overflow_good_response_status(monkeypatch): 209 | ''' 210 | Ensures that BeautifulSoup is returned from query_stack_overflow. 211 | ''' 212 | 213 | # 1. Given. 214 | path = 'autostack/so_web_scraper/__tests__/data/query_post_summaries.html' 215 | html = open(path).read() 216 | soup = BeautifulSoup(html, 'lxml') 217 | mock_response = MockResponse( 218 | path, 219 | 200 220 | ) 221 | mock_get = build_mock_get(mock_response) 222 | 223 | monkeypatch.setattr('requests.get', mock_get) 224 | 225 | # 2. When. 226 | response = query_stack_overflow(None) 227 | 228 | # 3. Then. 229 | assert response == soup 230 | 231 | 232 | def test_query_stack_overflow_bad_response_status(monkeypatch): 233 | ''' 234 | Ensures that BeautifulSoup is returned from query_stack_overflow. 235 | ''' 236 | 237 | # 1. Given. 238 | mock_response = MockResponse( 239 | 'autostack/so_web_scraper/__tests__/data/query_post_summaries.html', 240 | 400 241 | ) 242 | mock_get = build_mock_get(mock_response) 243 | 244 | monkeypatch.setattr('requests.get', mock_get) 245 | 246 | # 2. When. 247 | response = query_stack_overflow(None) 248 | 249 | # 3. Then. 250 | assert not response 251 | 252 | 253 | def test_post_soup_no_accepted_answer(monkeypatch): 254 | ''' 255 | Ensures that None is returned when there's no accepted answer. 256 | ''' 257 | 258 | # 1. Given. 259 | def mock_has_accepted_answer(*args): 260 | # pylint: disable=unused-argument 261 | ''' 262 | Mocks the has_accepted_answer function. 263 | ''' 264 | 265 | return False 266 | 267 | monkeypatch.setattr( 268 | 'autostack.so_web_scraper.has_accepted_answer', 269 | mock_has_accepted_answer 270 | ) 271 | 272 | # 2. When. 273 | response_soup = post_soup(None) 274 | 275 | # 3. Then. 276 | assert not response_soup 277 | 278 | 279 | def test_post_soup_accepted_answer(monkeypatch): 280 | ''' 281 | Ensures that BeautifulSoup is returned when there's an accepted answer. 282 | ''' 283 | 284 | # 1. Given. 285 | path = 'autostack/so_web_scraper/__tests__/data/post_accepted_answer.html' 286 | html = open(path).read() 287 | soup = BeautifulSoup(html, 'lxml') 288 | 289 | def mock_has_accepted_answer(*args): 290 | # pylint: disable=unused-argument 291 | ''' 292 | Mocks the has_accepted_answer function. 293 | ''' 294 | 295 | return True 296 | 297 | def mock_get_post_url(*args): 298 | # pylint: disable=unused-argument 299 | ''' 300 | Mocks the get_post_url function. 301 | ''' 302 | 303 | return '' 304 | 305 | mock_response = MockResponse( 306 | path, 307 | 200 308 | ) 309 | mock_get = build_mock_get(mock_response) 310 | 311 | monkeypatch.setattr( 312 | 'autostack.so_web_scraper.has_accepted_answer', 313 | mock_has_accepted_answer 314 | ) 315 | 316 | monkeypatch.setattr( 317 | 'autostack.so_web_scraper.get_post_url', 318 | mock_get_post_url 319 | ) 320 | 321 | monkeypatch.setattr('requests.get', mock_get) 322 | 323 | # 2. When. 324 | response_soup = post_soup(None) 325 | 326 | # 3. Then. 327 | assert response_soup == soup 328 | 329 | 330 | def test_post_soup_bad_status(monkeypatch): 331 | ''' 332 | Ensures that None is returned when the request status is bad. 333 | ''' 334 | 335 | # 1. Given. 336 | mock_response = MockResponse( 337 | 'autostack/so_web_scraper/__tests__/data/post_accepted_answer.html', 338 | 400 339 | ) 340 | mock_get = build_mock_get(mock_response) 341 | 342 | def mock_has_accepted_answer(*args): 343 | # pylint: disable=unused-argument 344 | ''' 345 | Mocks the has_accepted_answer function. 346 | ''' 347 | 348 | return True 349 | 350 | def mock_get_post_url(*args): 351 | # pylint: disable=unused-argument 352 | ''' 353 | Mocks the get_post_url function. 354 | ''' 355 | 356 | return '' 357 | 358 | monkeypatch.setattr( 359 | 'autostack.so_web_scraper.has_accepted_answer', 360 | mock_has_accepted_answer 361 | ) 362 | 363 | monkeypatch.setattr( 364 | 'autostack.so_web_scraper.get_post_url', 365 | mock_get_post_url 366 | ) 367 | 368 | monkeypatch.setattr('requests.get', mock_get) 369 | 370 | # 2. When. 371 | response = post_soup(None) 372 | 373 | # 3. Then. 374 | assert not response 375 | 376 | 377 | def test_has_accepted_answer_false(): 378 | ''' 379 | Ensures that has_accepted_answer returns False when the post 380 | does not have an accepted answer. 381 | ''' 382 | 383 | # 1. Given. 384 | html = open( 385 | 'autostack/so_web_scraper/__tests__/data/query_post_summaries.html' 386 | ).read() 387 | 388 | post_summary = BeautifulSoup(html, 'lxml').find_all( 389 | attrs={ 390 | 'class': 'question-summary' 391 | } 392 | )[0] 393 | 394 | # 2. When. 395 | accepted_answer = has_accepted_answer(post_summary) 396 | 397 | # 3. Then. 398 | assert not accepted_answer 399 | 400 | 401 | def test_has_accepted_answer_true(): 402 | ''' 403 | Ensures that has_accepted_answer returns True when the post 404 | does in fact have an accepted answer. 405 | ''' 406 | 407 | # 1. Given. 408 | html = open( 409 | 'autostack/so_web_scraper/__tests__/data/query_post_summaries.html' 410 | ).read() 411 | 412 | post_summary = BeautifulSoup(html, 'lxml').find_all( 413 | attrs={ 414 | 'class': 'question-summary' 415 | } 416 | )[4] 417 | 418 | # 2. When. 419 | accepted_answer = has_accepted_answer(post_summary) 420 | 421 | # 3. Then. 422 | assert accepted_answer 423 | 424 | 425 | def test_get_post_url_where_url_exists(): 426 | ''' 427 | Ensures that a url is returned from get_post_url 428 | when a url exists. 429 | ''' 430 | 431 | # 1. Given. 432 | html = open( 433 | 'autostack/so_web_scraper/__tests__/data/query_post_summaries.html' 434 | ).read() 435 | 436 | post_summary = BeautifulSoup(html, 'lxml').find( 437 | attrs={ 438 | 'class': 'question-summary' 439 | } 440 | ) 441 | 442 | # 2. When. 443 | url = get_post_url(post_summary) 444 | 445 | # 3. Then. 446 | # pylint: disable=line-too-long 447 | assert url == '/questions/930397/getting-the-last-element-of-a-list/930398?r=SearchResults#930398' # nopep8 448 | 449 | 450 | def test_get_post_url_where_url_doesnt_exist(): 451 | ''' 452 | Ensures that None is returned from get_post_url 453 | when a url doesn't exist. 454 | ''' 455 | 456 | # 1. Given. 457 | # pylint: disable=too-few-public-methods 458 | class MockPostSummary: 459 | ''' 460 | Mocks a post summary. 461 | ''' 462 | 463 | def find(self, *args, **kwargs): 464 | # pylint: disable=unused-argument,no-self-use 465 | ''' 466 | Returns an empty dictionary, mocking a find call on a 467 | bs4.Tag. 468 | ''' 469 | 470 | return dict() 471 | 472 | mock_post_summary = MockPostSummary() 473 | 474 | # 2. When. 475 | url = get_post_url(mock_post_summary) 476 | 477 | # 3. Then. 478 | assert not url 479 | 480 | 481 | def test_print_accepted_post_no_question(capsys, monkeypatch): 482 | ''' 483 | Ensures that nothing is printed when no question is found on a post. 484 | ''' 485 | 486 | # 1. Given. 487 | def mock_get_post_text(*args): 488 | ''' 489 | Mocks the get_post_text function. 490 | ''' 491 | 492 | if args[1] == 'question': 493 | return None 494 | return True 495 | 496 | monkeypatch.setattr( 497 | 'autostack.so_web_scraper.get_post_text', 498 | mock_get_post_text 499 | ) 500 | 501 | # 2. When. 502 | print_accepted_post(None) 503 | 504 | # 3. Then. 505 | captured = capsys.readouterr() 506 | assert not captured.out 507 | 508 | 509 | def test_print_accepted_post_no_answer(capsys, monkeypatch): 510 | ''' 511 | Ensures that nothing is printed when no answer is found on a post. 512 | ''' 513 | 514 | # 1. Given. 515 | def mock_get_post_text(*args): 516 | ''' 517 | Mocks the get_post_text function. 518 | ''' 519 | 520 | if args[1] == 'accepted-answer': 521 | return None 522 | return True 523 | 524 | monkeypatch.setattr( 525 | 'autostack.so_web_scraper.get_post_text', 526 | mock_get_post_text 527 | ) 528 | 529 | # 2. When. 530 | print_accepted_post(None) 531 | 532 | # 3. Then. 533 | captured = capsys.readouterr() 534 | assert not captured.out 535 | 536 | 537 | def test_print_accepted_post_found_question_and_answer(capsys, monkeypatch): 538 | ''' 539 | Ensures that proper output when both a question and answer are found. 540 | ''' 541 | 542 | # 1. Given. 543 | def mock_get_post_text(*args): 544 | # pylint: disable=unused-argument 545 | ''' 546 | Mocks the get_post_text function. 547 | ''' 548 | 549 | return True 550 | 551 | def mock_print_post_text(*args): 552 | # pylint: disable=unused-argument 553 | ''' 554 | Mocks the print_post_text function. 555 | ''' 556 | 557 | return 558 | 559 | monkeypatch.setattr( 560 | 'autostack.so_web_scraper.get_post_text', 561 | mock_get_post_text 562 | ) 563 | 564 | monkeypatch.setattr( 565 | 'autostack.so_web_scraper.print_post_text', 566 | mock_print_post_text 567 | ) 568 | 569 | # 2. When. 570 | print_accepted_post(None) 571 | 572 | # 3. Then. 573 | captured = capsys.readouterr() 574 | assert ANSI_ESCAPE.sub('', captured.out) == ( 575 | '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n' + 576 | 'Question:\n' + 577 | '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n' + 578 | 'Answer:\n' + 579 | '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n' 580 | ) 581 | 582 | 583 | def test_get_post_text_question(): 584 | ''' 585 | Ensures that the question post-text is returned for a post. 586 | ''' 587 | 588 | # 1. Given. 589 | path = 'autostack/so_web_scraper/__tests__/data/post_accepted_answer.html' 590 | html = open(path).read() 591 | post = BeautifulSoup(html, 'lxml') 592 | 593 | # 2. When. 594 | post_text = get_post_text(post, 'question') 595 | 596 | # 3. Then. 597 | assert post_text 598 | 599 | 600 | def test_get_post_text_answer(): 601 | ''' 602 | Ensures the accepted answer post-text is returned for a post. 603 | ''' 604 | 605 | # 1. Given. 606 | path = 'autostack/so_web_scraper/__tests__/data/post_accepted_answer.html' 607 | html = open(path).read() 608 | post = BeautifulSoup(html, 'lxml') 609 | 610 | # 2. When. 611 | post_text = get_post_text(post, 'accepted-answer') 612 | 613 | # 3. Then. 614 | assert post_text 615 | 616 | 617 | def test_get_post_text_invalid_html_class(): 618 | ''' 619 | Ensures that None when an Attribute error occures. 620 | ''' 621 | 622 | # 1. Given. 623 | # pylint: disable=too-few-public-methods 624 | class MockPost: 625 | ''' 626 | Mocks a post. 627 | ''' 628 | 629 | def find(self, *args, **kwargs): 630 | # pylint: disable=unused-argument,no-self-use 631 | ''' 632 | Returns None, mocking a find call on a bs4.Tag. 633 | ''' 634 | 635 | return None 636 | 637 | mock_post = MockPost() 638 | 639 | # 2. When. 640 | post_text = get_post_text(mock_post, 'invalid-class') 641 | 642 | # 3. Then. 643 | assert not post_text 644 | 645 | 646 | def test_print_post_text(capsys, monkeypatch): 647 | ''' 648 | Ensures that proper output when print_post_text is called. 649 | ''' 650 | 651 | # 1. Given. 652 | path = 'autostack/so_web_scraper/__tests__/data/post_text.html' 653 | html = open(path).read() 654 | post_text = BeautifulSoup(html, 'lxml').find( 655 | attrs={'class', 'post-text'} 656 | ) 657 | 658 | def mock_other_print_functions(*args): 659 | # pylint: disable=unused-argument 660 | ''' 661 | Mocks print_ul and print_code_block functions. 662 | ''' 663 | 664 | return 665 | 666 | monkeypatch.setattr( 667 | 'autostack.so_web_scraper.print_ul', 668 | mock_other_print_functions 669 | ) 670 | 671 | monkeypatch.setattr( 672 | 'autostack.so_web_scraper.print_code_block', 673 | mock_other_print_functions 674 | ) 675 | 676 | # 2. When. 677 | print_post_text(post_text) 678 | 679 | # 3. Then. 680 | captured = capsys.readouterr() 681 | assert ANSI_ESCAPE.sub('', captured.out) == ( 682 | 'Test 1\n' + 683 | 'Test 2\n' + 684 | 'Test 3\n' + 685 | 'Test 4\n' + 686 | 'Test 5\n' 687 | ) 688 | 689 | 690 | def test_print_ul_populated(capsys): 691 | ''' 692 | Ensures that all list elements are printed. 693 | ''' 694 | 695 | # 1. Given. 696 | path = ( 697 | 'autostack/so_web_scraper/__tests__/data/post_text_ul_populated.html' 698 | ) 699 | html = open(path).read() 700 | unordered_list = BeautifulSoup(html, 'lxml').find('ul') 701 | 702 | # 2. When. 703 | print_ul(unordered_list) 704 | 705 | # 3. Then. 706 | captured = capsys.readouterr() 707 | assert ANSI_ESCAPE.sub('', captured.out) == ( 708 | ' - Test 1\n' + 709 | ' - Test 2\n' + 710 | ' - Test 3\n' 711 | ) 712 | 713 | 714 | def test_print_ul_empty(capsys): 715 | ''' 716 | Ensures that nothing is printed when the unordered list is empty. 717 | ''' 718 | 719 | # 1. Given. 720 | path = 'autostack/so_web_scraper/__tests__/data/post_text_ul_empty.html' 721 | html = open(path).read() 722 | unordered_list = BeautifulSoup(html, 'lxml').find('ul') 723 | 724 | # 2. When. 725 | print_ul(unordered_list) 726 | 727 | # 3. Then. 728 | captured = capsys.readouterr() 729 | assert not captured.out 730 | 731 | 732 | def test_print_code_block(capsys, monkeypatch): 733 | ''' 734 | Ensures that all of the source code is printed. 735 | ''' 736 | 737 | # 1. Given. 738 | line_1 = 'l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]\n' 739 | line_2 = 'reduce(lambda x, y: x.extend(y), l)' 740 | 741 | def mock_get_src_code(*args): 742 | # pylint: disable=unused-argument 743 | ''' 744 | Mocks the get_src_code function. 745 | ''' 746 | 747 | return 748 | 749 | def mock_lex(*args): 750 | # pylint: disable=unused-argument 751 | ''' 752 | Mocks the pygments.lex function. 753 | ''' 754 | 755 | nonlocal line_1 756 | nonlocal line_2 757 | 758 | responses = [ 759 | ('Token.Keyword', line_1), 760 | ('Other', line_2) 761 | ] 762 | 763 | for i in range(2): 764 | yield responses[i] 765 | 766 | monkeypatch.setattr( 767 | 'autostack.so_web_scraper.get_src_code', 768 | mock_get_src_code 769 | ) 770 | 771 | monkeypatch.setattr('pygments.lex', mock_lex) 772 | 773 | # 2. When. 774 | print_code_block(None) 775 | 776 | # 3. Then. 777 | captured = capsys.readouterr() 778 | assert ANSI_ESCAPE.sub('', captured.out) == ( 779 | '\n{}'.format(line_1) + 780 | '{}\n'.format(line_2) 781 | ) 782 | 783 | 784 | def test_get_src_code(): 785 | ''' 786 | Ensures that all source code is returned. 787 | ''' 788 | 789 | # 1. Given. 790 | line_1 = 'l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]\n' 791 | line_2 = 'reduce(lambda x, y: x.extend(y), l)' 792 | path = 'autostack/so_web_scraper/__tests__/data/post_text_code.html' 793 | html = open(path).read() 794 | code_block = BeautifulSoup(html, 'lxml').find('div').find('code') 795 | 796 | # 2. When. 797 | src_code = get_src_code(code_block) 798 | 799 | # 3. Then. 800 | assert src_code == line_1 + line_2 801 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | development= 2 | while getopts d switch 3 | do 4 | case $switch in 5 | d) development=1;; 6 | ?) printf "Usage: %s: [-d]\n" $0; exit 2;; 7 | esac 8 | done 9 | 10 | if [ $development ]; then 11 | pip3 install -e .[development] 12 | else 13 | pip3 install -e . 14 | fi 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pygments==2.3.1 2 | requests==2.21.0 3 | termcolor==1.1.0 4 | lxml==4.3.2 5 | beautifulsoup4==4.7.1 6 | future==0.17.1 7 | pytest==5.0.1 8 | pytest-pep8==1.0.6 9 | pytest-cov==2.7.1 10 | pytest-pylint==0.14.1 11 | Click==7.0 12 | PyInquirer==1.0.3 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | from os import path 3 | 4 | setuptools.setup( 5 | # General setup information. 6 | name='autostack', 7 | version='1.2.1', 8 | packages=setuptools.find_packages(exclude=['tests']), 9 | entry_points={ 10 | 'console_scripts': [ 11 | 'autostack=autostack.main:main' 12 | ] 13 | }, 14 | install_requires=[ 15 | 'Pygments', 16 | 'requests', 17 | 'termcolor', 18 | 'lxml', 19 | 'beautifulsoup4', 20 | 'future', 21 | 'pytest', 22 | 'pytest-cov', 23 | 'click', 24 | 'PyInquirer', 25 | ], 26 | extras_require={ 27 | 'development': [ 28 | 'pytest', 29 | 'pytest-pep8', 30 | 'pytest-cov', 31 | 'pytest-pylint', 32 | ] 33 | }, 34 | # Meta data. 35 | url='https://github.com/elijahsawyers/autostack', 36 | author='Elijah Sawyers, Benjamin Sanders, Caleb Werth', 37 | author_email='elijahsawyers@gmail.com, ben.sanders97@gmail.com, cwerth@crimson.ua.edu', 38 | description='Automatically detect python errors and search Stack Overflow.', 39 | long_description_content_type='text/markdown', 40 | long_description=open('./README.md').read(), 41 | classifiers=[ 42 | 'Development Status :: 5 - Production/Stable', 43 | 'Intended Audience :: Developers', 44 | 'Topic :: Software Development :: Debuggers', 45 | 'Programming Language :: Python :: 3', 46 | 'License :: OSI Approved :: MIT License', 47 | 'Operating System :: Unix', 48 | ], 49 | keywords='command-line debug development tool' 50 | ) -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | rm /usr/local/bin/autostack 2 | rm /usr/local/bin/autostack-terminal 3 | rm -rf autostack.egg-info --------------------------------------------------------------------------------