├── .gitignore ├── .travis.yml ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.md ├── README.rst ├── _config.yml ├── gitsuggest ├── __init__.py ├── commandline.py ├── gitlang │ ├── languages.txt │ └── others.txt ├── res │ ├── base.htm.j2 │ ├── logo.png │ ├── page.template │ ├── result.template │ └── suggest.htm.j2 ├── suggest.py └── utilities.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── mockentities.py ├── test_suggest.py └── test_utilities.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Files to ignore. 2 | *.pyc 3 | .Python 4 | pip-selfcheck.json 5 | 6 | # Directories to ignore. 7 | bin/ 8 | include/ 9 | lib/ 10 | git_suggest/__pycache__/ 11 | 12 | # PyPi related directories to ignore. 13 | *.egg-info/ 14 | dist/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | # Command to install dependencies. 8 | install: 9 | - "pip install -r requirements.txt" 10 | - "pip install coveralls" 11 | # Command to run tests. 12 | script: 13 | - "coverage run --source=gitsuggest setup.py test" 14 | after_success: 15 | - "coveralls" 16 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | v0.0.13 2 | ------- 3 | * Better error message for 2FA authentication. 4 | * Cleaner UI to display the repositories. 5 | * More information about the repositories like the language and number of stars. 6 | * Making the UI same as UI for http://www.gitsuggest.com 7 | * Using Jinja2 for templating. 8 | 9 | v0.0.12 10 | ------- 11 | * 0 stars exception fix. 12 | 13 | v0.0.11 14 | ------- 15 | * Use sets instead of lists for speedup. 16 | 17 | v0.0.10 18 | ------- 19 | * Removing dependency on pyenchant and using nltk instead for english words. 20 | * Adding nltk corpus downloads as post installation tasks in setup.py. 21 | 22 | v0.0.9 23 | ------ 24 | * `--deep_dive` flag to to provide user with control over accuracy vs time. 25 | * Access token based authentication for procuring repository reccomendations. 26 | * Prettier command line display. 27 | 28 | v0.0.8 29 | ------ 30 | * Fixes to setup script to ensure package data is copied over. 31 | 32 | v0.0.7 33 | ------ 34 | * Fixes to ensure proper packaging of resources instead of relying on 35 | submodules. 36 | 37 | v0.0.4, v0.0.5, v0.0.6 (Unstable) 38 | --------------------------------- 39 | * Smart pagination usage for much faster response times. 40 | * py3 bug fixes to make it more stable. 41 | * Provision of a way to get suggestions without password. Even though it is 42 | contingent on the rate limit that is applied. 43 | 44 | v0.0.3 45 | ------ 46 | * Removal of password from command line for better security. 47 | 48 | v0.0.2 49 | ------ 50 | * Some major bug fixes causing slowdown. 51 | 52 | 53 | v0.0.1 54 | ------ 55 | * Long time itch to code an idea. 56 | * First release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vishwas B Sharma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE CHANGELOG.rst 2 | include gitsuggest/gitlang/* 3 | include gitsuggest/res/* 4 | recursive-include tests *.py -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | 10 | gensim = "*" 11 | PyGithub = "*" 12 | nltk = "*" 13 | crayons = "*" 14 | "Jinja2" = "*" -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "265c02cbfb79252002f4bacd0cad0f840fc89937238ceceaa57e3d7c78aa3b7a" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.python.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "boto": { 18 | "hashes": [ 19 | "sha256:147758d41ae7240dc989f0039f27da8ca0d53734be0eb869ef16e3adcfa462e8", 20 | "sha256:ea0d3b40a2d852767be77ca343b58a9e3a4b00d9db440efb8da74b4e58025e5a" 21 | ], 22 | "version": "==2.49.0" 23 | }, 24 | "boto3": { 25 | "hashes": [ 26 | "sha256:839285fbd6f3ab16170af449ae9e33d0eccf97ca22de17d9ff68b8da2310ea06", 27 | "sha256:d93f1774c4bc66e02acdda2067291acb9e228a035435753cb75f83ad2904cbe3" 28 | ], 29 | "version": "==1.9.253" 30 | }, 31 | "botocore": { 32 | "hashes": [ 33 | "sha256:3baf129118575602ada9926f5166d82d02273c250d0feb313fc270944b27c48b", 34 | "sha256:dc080aed4f9b220a9e916ca29ca97a9d37e8e1d296fe89cbaeef929bf0c8066b" 35 | ], 36 | "version": "==1.12.253" 37 | }, 38 | "certifi": { 39 | "hashes": [ 40 | "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", 41 | "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" 42 | ], 43 | "version": "==2019.9.11" 44 | }, 45 | "chardet": { 46 | "hashes": [ 47 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 48 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 49 | ], 50 | "version": "==3.0.4" 51 | }, 52 | "colorama": { 53 | "hashes": [ 54 | "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", 55 | "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" 56 | ], 57 | "version": "==0.4.1" 58 | }, 59 | "crayons": { 60 | "hashes": [ 61 | "sha256:41f0843815a8e3ac6fb445b7970d8b9c766e6f164092d84e7ea809b4c91418ec", 62 | "sha256:8edcadb7f197e25f2cc094aec5bf7f1b6001d3f76c82d56f8d46f6fb1405554f" 63 | ], 64 | "index": "pypi", 65 | "version": "==0.2.0" 66 | }, 67 | "deprecated": { 68 | "hashes": [ 69 | "sha256:a515c4cf75061552e0284d123c3066fbbe398952c87333a92b8fc3dd8e4f9cc1", 70 | "sha256:b07b414c8aac88f60c1d837d21def7e83ba711052e03b3cbaff27972567a8f8d" 71 | ], 72 | "version": "==1.2.6" 73 | }, 74 | "docutils": { 75 | "hashes": [ 76 | "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", 77 | "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", 78 | "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" 79 | ], 80 | "version": "==0.15.2" 81 | }, 82 | "gensim": { 83 | "hashes": [ 84 | "sha256:195c13831e00ab605eced76016aaeafe55eba26077d47d998c9b92330a68a3bf", 85 | "sha256:2f7c9554a357c4a38ea9907a4bb3d53d7d95d6c4c6e778713c3fd744f1e7ae36", 86 | "sha256:320eda2de98e014367988d1d1b037080371b491b71ad92d6022877466e2f4342", 87 | "sha256:38018fac787cb1ca3756dfec6d3adf32c18141786d1b6bafbffb088851fc01d5", 88 | "sha256:4163da797de83561adc3897c88fe5b2a15df61703382bd0747a0aabe5dbda717", 89 | "sha256:44e03d5eabc28fd7866796f4c6d01496500601f23ccfa3b84750355d46f244d1", 90 | "sha256:6a53e6fea1ebd014eb0d29d4ae4119523bc8d21674ce4468138cdba3bc2ebee0", 91 | "sha256:74b540785a6044daec3011c72f0a1b2c67a7adb02741bebb066cb0623d611b8e", 92 | "sha256:894c85a7cc217884c8879f87545ffa22cce691f871bdd9972f177e2151f26d15", 93 | "sha256:94a45de3d72fed7825af8584b9356df8d1dd553a89c4291d90f9ce5bc9f143c0", 94 | "sha256:9c60c14fe19667af485c4ad85e20cf19ffd50a23705dad08b70b4d0c7ac66545", 95 | "sha256:d40bc9fea774cfb9e20deab6f36e5b656e2be2afe3ad086a7ec430c33cc02cd6", 96 | "sha256:e7467d41873eaf31e441654157b82b0d6c70753aec2eca0a8c31adb7f3154fc1", 97 | "sha256:ef0df84a7b10f4956dbe3b7ab8a6459c0d5ab9f2100771019997631bb419eae6", 98 | "sha256:f82b95757e58f36300cd3ead0091781d5e8be869f5974721660bddee79bd1913" 99 | ], 100 | "index": "pypi", 101 | "version": "==3.7.2" 102 | }, 103 | "idna": { 104 | "hashes": [ 105 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 106 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 107 | ], 108 | "version": "==2.8" 109 | }, 110 | "jinja2": { 111 | "hashes": [ 112 | "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", 113 | "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" 114 | ], 115 | "index": "pypi", 116 | "version": "==2.10.1" 117 | }, 118 | "jmespath": { 119 | "hashes": [ 120 | "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", 121 | "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" 122 | ], 123 | "version": "==0.9.4" 124 | }, 125 | "markupsafe": { 126 | "hashes": [ 127 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 128 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 129 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 130 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 131 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 132 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 133 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 134 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 135 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 136 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 137 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 138 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 139 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 140 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 141 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 142 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 143 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 144 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 145 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 146 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 147 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 148 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 149 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 150 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 151 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 152 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 153 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 154 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" 155 | ], 156 | "version": "==1.1.1" 157 | }, 158 | "nltk": { 159 | "hashes": [ 160 | "sha256:bed45551259aa2101381bbdd5df37d44ca2669c5c3dad72439fa459b29137d94" 161 | ], 162 | "index": "pypi", 163 | "version": "==3.4.5" 164 | }, 165 | "numpy": { 166 | "hashes": [ 167 | "sha256:0b0dd8f47fb177d00fa6ef2d58783c4f41ad3126b139c91dd2f7c4b3fdf5e9a5", 168 | "sha256:25ffe71f96878e1da7e014467e19e7db90ae7d4e12affbc73101bcf61785214e", 169 | "sha256:26efd7f7d755e6ca966a5c0ac5a930a87dbbaab1c51716ac26a38f42ecc9bc4b", 170 | "sha256:28b1180c758abf34a5c3fea76fcee66a87def1656724c42bb14a6f9717a5bdf7", 171 | "sha256:2e418f0a59473dac424f888dd57e85f77502a593b207809211c76e5396ae4f5c", 172 | "sha256:30c84e3a62cfcb9e3066f25226e131451312a044f1fe2040e69ce792cb7de418", 173 | "sha256:4650d94bb9c947151737ee022b934b7d9a845a7c76e476f3e460f09a0c8c6f39", 174 | "sha256:4dd830a11e8724c9c9379feed1d1be43113f8bcce55f47ea7186d3946769ce26", 175 | "sha256:4f2a2b279efde194877aff1f76cf61c68e840db242a5c7169f1ff0fd59a2b1e2", 176 | "sha256:62d22566b3e3428dfc9ec972014c38ed9a4db4f8969c78f5414012ccd80a149e", 177 | "sha256:669795516d62f38845c7033679c648903200980d68935baaa17ac5c7ae03ae0c", 178 | "sha256:75fcd60d682db3e1f8fbe2b8b0c6761937ad56d01c1dc73edf4ef2748d5b6bc4", 179 | "sha256:9395b0a41e8b7e9a284e3be7060db9d14ad80273841c952c83a5afc241d2bd98", 180 | "sha256:9e37c35fc4e9410093b04a77d11a34c64bf658565e30df7cbe882056088a91c1", 181 | "sha256:a0678793096205a4d784bd99f32803ba8100f639cf3b932dc63b21621390ea7e", 182 | "sha256:b46554ad4dafb2927f88de5a1d207398c5385edbb5c84d30b3ef187c4a3894d8", 183 | "sha256:c867eeccd934920a800f65c6068acdd6b87e80d45cd8c8beefff783b23cdc462", 184 | "sha256:dd0667f5be56fb1b570154c2c0516a528e02d50da121bbbb2cbb0b6f87f59bc2", 185 | "sha256:de2b1c20494bdf47f0160bd88ed05f5e48ae5dc336b8de7cfade71abcc95c0b9", 186 | "sha256:f1df7b2b7740dd777571c732f98adb5aad5450aee32772f1b39249c8a50386f6", 187 | "sha256:ffca69e29079f7880c5392bf675eb8b4146479d976ae1924d01cd92b04cccbcc" 188 | ], 189 | "version": "==1.17.3" 190 | }, 191 | "pygithub": { 192 | "hashes": [ 193 | "sha256:74cbc6a0518bc7f4a8a1c047c954592abe622b735f1352ea30201668607fbe24" 194 | ], 195 | "index": "pypi", 196 | "version": "==1.43.6" 197 | }, 198 | "pyjwt": { 199 | "hashes": [ 200 | "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", 201 | "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" 202 | ], 203 | "version": "==1.7.1" 204 | }, 205 | "python-dateutil": { 206 | "hashes": [ 207 | "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", 208 | "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" 209 | ], 210 | "markers": "python_version >= '2.7'", 211 | "version": "==2.8.0" 212 | }, 213 | "requests": { 214 | "hashes": [ 215 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 216 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 217 | ], 218 | "version": "==2.22.0" 219 | }, 220 | "s3transfer": { 221 | "hashes": [ 222 | "sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d", 223 | "sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba" 224 | ], 225 | "version": "==0.2.1" 226 | }, 227 | "scipy": { 228 | "hashes": [ 229 | "sha256:0baa64bf42592032f6f6445a07144e355ca876b177f47ad8d0612901c9375bef", 230 | "sha256:243b04730d7223d2b844bda9500310eecc9eda0cba9ceaf0cde1839f8287dfa8", 231 | "sha256:2643cfb46d97b7797d1dbdb6f3c23fe3402904e3c90e6facfe6a9b98d808c1b5", 232 | "sha256:396eb4cdad421f846a1498299474f0a3752921229388f91f60dc3eda55a00488", 233 | "sha256:3ae3692616975d3c10aca6d574d6b4ff95568768d4525f76222fb60f142075b9", 234 | "sha256:435d19f80b4dcf67dc090cc04fde2c5c8a70b3372e64f6a9c58c5b806abfa5a8", 235 | "sha256:46a5e55850cfe02332998b3aef481d33f1efee1960fe6cfee0202c7dd6fc21ab", 236 | "sha256:75b513c462e58eeca82b22fc00f0d1875a37b12913eee9d979233349fce5c8b2", 237 | "sha256:7ccfa44a08226825126c4ef0027aa46a38c928a10f0a8a8483c80dd9f9a0ad44", 238 | "sha256:89dd6a6d329e3f693d1204d5562dd63af0fd7a17854ced17f9cbc37d5b853c8d", 239 | "sha256:a81da2fe32f4eab8b60d56ad43e44d93d392da228a77e229e59b51508a00299c", 240 | "sha256:a9d606d11eb2eec7ef893eb825017fbb6eef1e1d0b98a5b7fc11446ebeb2b9b1", 241 | "sha256:ac37eb652248e2d7cbbfd89619dce5ecfd27d657e714ed049d82f19b162e8d45", 242 | "sha256:cbc0611699e420774e945f6a4e2830f7ca2b3ee3483fca1aa659100049487dd5", 243 | "sha256:d02d813ec9958ed63b390ded463163685af6025cb2e9a226ec2c477df90c6957", 244 | "sha256:dd3b52e00f93fd1c86f2d78243dfb0d02743c94dd1d34ffea10055438e63b99d" 245 | ], 246 | "version": "==1.3.1" 247 | }, 248 | "six": { 249 | "hashes": [ 250 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 251 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 252 | ], 253 | "version": "==1.12.0" 254 | }, 255 | "smart-open": { 256 | "hashes": [ 257 | "sha256:788e07f035defcbb62e3c1e313329a70b0976f4f65406ee767db73ad5d2d04f9" 258 | ], 259 | "version": "==1.8.4" 260 | }, 261 | "urllib3": { 262 | "hashes": [ 263 | "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", 264 | "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" 265 | ], 266 | "markers": "python_version >= '3.4'", 267 | "version": "==1.25.6" 268 | }, 269 | "wrapt": { 270 | "hashes": [ 271 | "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" 272 | ], 273 | "version": "==1.11.2" 274 | } 275 | }, 276 | "develop": {} 277 | } 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitsuggest 2 | 3 | [![pypiv](https://img.shields.io/pypi/v/gitsuggest.svg)](https://pypi.python.org/pypi/gitsuggest) 4 | [![pyv](https://img.shields.io/pypi/pyversions/gitsuggest.svg)](https://pypi.python.org/pypi/gitsuggest) 5 | [![Licence](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/csurfer/gitsuggest/master/LICENSE) 6 | [![Thanks](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/csurfer) 7 | 8 | A tool to suggest github repositories based on the repositories you have shown interest in. 9 | 10 | ![Demo](https://i.imgur.com/3AfvTxt.gif) 11 | 12 | ## Whats happening here? 13 | 14 | > Programs must be written for people to read, and only incidentally for machines to execute. ~ Hal Abelson 15 | 16 | One quick way to become a better programmer is by reading code written by smart people. Github makes finding such code/repositories easy. At the end of the day we all are interested in our own specific areas and we express this interest by “starring” repositories and/or “following” people who contribute to such repositories. 17 | 18 | This is a tool which uses the repositories you have starred and the repositories that people you follow have starred to help you discover repositories which might be of interest to you. 19 | 20 | ## How fast is it? 21 | 22 | It totally depends on the number of repositories you have, and people you follow have starred. Based on this number it might take anywhere between a minute to two minutes to give you the list of suggested repositories. 23 | 24 | ## Setup 25 | 26 | ### Using pip 27 | 28 | ```bash 29 | pip install gitsuggest 30 | ``` 31 | 32 | ### Directly from the repository 33 | 34 | ```bash 35 | git clone --recursive https://github.com/csurfer/gitsuggest.git 36 | python gitsuggest/setup.py install 37 | ``` 38 | 39 | ## Post setup 40 | 41 | If you see a stopwords error, it means that you do not have the corpus stopwords downloaded from NLTK. You can download it using command below. 42 | 43 | ```bash 44 | python -c "import nltk; nltk.download('stopwords')" 45 | ``` 46 | 47 | ## Usage 48 | 49 | ### As a command 50 | 51 | ```bash 52 | # For help with usage 53 | gitsuggest --help 54 | 55 | # With just username in command to provide password secretly 56 | gitsuggest 57 | 58 | # Password can be skipped which means you chose to go the unauthenticated 59 | # way which may raise RateLimitExceeded exception.To skip password enter 60 | # nothing when prompted for password and press enter. 61 | 62 | # NOTE: Using it this way generates a static html page with the search 63 | # results. This gets opened it in your default browser. 64 | 65 | # To fetch better suggestions at the cost of extra time and calls 66 | gitsuggest --deep_dive 67 | 68 | # Without the --deep_dive flag we limit the suggestion to consider only 69 | # the repositories you have starred. With --deep_dive we also consider 70 | # repositories which people you follow have starred. 71 | ``` 72 | 73 | ### As a module 74 | 75 | ```python 76 | from gitsuggest import GitSuggest 77 | 78 | # To use with username password combination 79 | gs = GitSuggest(username=, password=) 80 | 81 | # To use with access_token 82 | gs = GitSuggest(token=access_token) 83 | 84 | # To use without authenticating 85 | gs = GitSuggest(username=) 86 | 87 | # To use with deep dive flag 88 | gs = GitSuggest(username=, password=, token=None, deep_dive=True) 89 | gs = GitSuggest(token=access_token, deep_dive=True) 90 | gs = GitSuggest(username=, deep_dive=True) 91 | 92 | # To get an iterator over suggested repositories. 93 | gs.get_suggested_repositories() 94 | ``` 95 | 96 | ## FAQ 97 | 98 | **Why do we need to authenticate (with password) to get suggestions, I browse gihub all the time without authenticating?** 99 | 100 | You don’t. From v0.0.4 you can choose to procure suggestions without actually authenticating with a password, but know that **access to github through API is highly rate limited** and it is much lesser for unauthenticated requests when compared to authenticated ones. More details about [ratelimits](https://developer.github.com/v3/search/#rate-limit). 101 | 102 | What this means is that when used without a password (unauthenticated) it may fail with RateLimitExceeded exception. 103 | 104 | ## Contributing 105 | 106 | ### Bug Reports and Feature Requests 107 | 108 | Please use [issue tracker](https://github.com/csurfer/gitsuggest/issues) for reporting bugs or feature requests. 109 | 110 | ### Development 111 | 112 | Pull requests are most welcome. 113 | 114 | ### Buy the developer a cup of coffee! 115 | 116 | If you found the utility helpful you can buy me a cup of coffee using 117 | 118 | [![Donate](https://www.paypalobjects.com/webstatic/en_US/i/btn/png/silver-pill-paypal-44px.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=3BSBW7D45C4YN&lc=US¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted) 119 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | gitsuggest 2 | =========== 3 | 4 | |pypiv| |pyv| |Licence| |Thanks| 5 | 6 | A tool to suggest github repositories based on the repositories you have shown 7 | interest in. 8 | 9 | |Demo| 10 | 11 | Whats happening here? 12 | --------------------- 13 | 14 | ``Programs must be written for people to read, and only incidentally for 15 | machines to execute. ~ Hal Abelson`` 16 | 17 | One quick way to become a better programmer is by reading code written by smart 18 | people. Github makes finding such code/repositories easy. At the end of the day 19 | we all are interested in our own specific areas and we express this interest by 20 | "starring" repositories and/or "following" people who contribute to such 21 | repositories. 22 | 23 | This is a tool which uses the repositories you have starred and the repositories 24 | that people you follow have starred to help you discover repositories which 25 | might be of interest to you. 26 | 27 | How fast is it? 28 | --------------- 29 | 30 | It totally depends on the number of repositories you have, and people you follow 31 | have starred. Based on this number it might take anywhere between a minute to 32 | two minutes to give you the list of suggested repositories. 33 | 34 | Setup 35 | ----- 36 | 37 | Using pip 38 | ~~~~~~~~~ 39 | 40 | .. code:: bash 41 | 42 | pip install gitsuggest 43 | 44 | Directly from the repository 45 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 46 | 47 | .. code:: bash 48 | 49 | git clone --recursive https://github.com/csurfer/gitsuggest.git 50 | python gitsuggest/setup.py install 51 | 52 | Post setup 53 | ---------- 54 | 55 | If you see a stopwords error, it means that you do not have the corpus 56 | `stopwords` downloaded from NLTK. You can download it using command below. 57 | 58 | .. code:: bash 59 | 60 | python -c "import nltk; nltk.download('stopwords')" 61 | 62 | Usage 63 | ----- 64 | 65 | As a command 66 | ~~~~~~~~~~~~ 67 | 68 | .. code:: bash 69 | 70 | # For help with usage 71 | gitsuggest --help 72 | 73 | # With just username in command to provide password secretly 74 | gitsuggest 75 | 76 | # Password can be skipped which means you chose to go the unauthenticated 77 | # way which may raise RateLimitExceeded exception. To skip password enter 78 | # nothing when prompted for password and press enter. 79 | 80 | # NOTE: Using it this way generates a static html page with the search 81 | # results. This gets opened it in your default browser. 82 | 83 | # To fetch better suggestions at the cost of extra time and calls 84 | gitsuggest --deep_dive 85 | 86 | # Without the --deep_dive flag we limit the suggestion to consider only 87 | # the repositories you have starred. With --deep_dive we also consider 88 | # repositories which people you follow have starred. 89 | 90 | As a module 91 | ~~~~~~~~~~~ 92 | 93 | .. code:: python 94 | 95 | from gitsuggest import GitSuggest 96 | 97 | # To use with username password combination 98 | gs = GitSuggest(username=, password=) 99 | 100 | # To use with access_token 101 | gs = GitSuggest(token=access_token) 102 | 103 | # To use without authenticating 104 | gs = GitSuggest(username=) 105 | 106 | # To use with deep dive flag 107 | gs = GitSuggest(username=, password=, token=None, deep_dive=True) 108 | gs = GitSuggest(token=access_token, deep_dive=True) 109 | gs = GitSuggest(username=, deep_dive=True) 110 | 111 | # To get an iterator over suggested repositories. 112 | gs.get_suggested_repositories() 113 | 114 | FAQ 115 | --- 116 | 117 | **Why do we need to authenticate (with password) to get suggestions, I browse 118 | gihub all the time without authenticating?** 119 | 120 | You don't. From `v0.0.4` you can choose to procure suggestions without actually 121 | authenticating with a password, but know that **access to github through API is 122 | highly rate limited** and it is much lesser for unauthenticated requests when 123 | compared to authenticated ones. More details about `ratelimits`_. 124 | 125 | What this means is that when used without a password (unauthenticated) it may 126 | fail with `RateLimitExceeded` exception. 127 | 128 | Contributing 129 | ------------ 130 | 131 | Bug Reports and Feature Requests 132 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 133 | 134 | Please use `issue tracker`_ for reporting bugs or feature requests. 135 | 136 | Development 137 | ~~~~~~~~~~~ 138 | 139 | Pull requests are most welcome. 140 | 141 | Buy the developer a cup of coffee! 142 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 143 | 144 | If you found the utility helpful you can buy me a cup of coffee using 145 | 146 | |Donate| 147 | 148 | .. |Donate| image:: https://www.paypalobjects.com/webstatic/en_US/i/btn/png/silver-pill-paypal-44px.png 149 | :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=3BSBW7D45C4YN&lc=US¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted 150 | 151 | .. |Thanks| image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg 152 | :target: https://saythanks.io/to/csurfer 153 | 154 | .. _issue tracker: https://github.com/csurfer/gitsuggest/issues 155 | 156 | .. |pypiv| image:: https://img.shields.io/pypi/v/gitsuggest.svg 157 | :target: https://pypi.python.org/pypi/gitsuggest 158 | 159 | .. |pyv| image:: https://img.shields.io/pypi/pyversions/gitsuggest.svg 160 | :target: https://pypi.python.org/pypi/gitsuggest 161 | 162 | .. |Licence| image:: https://img.shields.io/badge/license-MIT-blue.svg 163 | :target: https://raw.githubusercontent.com/csurfer/gitsuggest/master/LICENSE 164 | 165 | .. |Demo| image:: https://i.imgur.com/3AfvTxt.gif 166 | 167 | .. _ratelimits: https://developer.github.com/v3/search/#rate-limit 168 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /gitsuggest/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # _______ __ _____ __ 4 | # / ____(_) /_/ ___/__ ______ _____ ____ _____/ /_ 5 | # / / __/ / __/\__ \/ / / / __ `/ __ `/ _ \/ ___/ __/ 6 | # / /_/ / / /_ ___/ / /_/ / /_/ / /_/ / __(__ ) /_ 7 | # \____/_/\__//____/\__,_/\__, /\__, /\___/____/\__/ 8 | # /____//____/ 9 | 10 | """ 11 | GitSuggest 12 | ~~~~~~~~~~ 13 | 14 | GitSuggest is a library written in Python, to suggest git repositories a user 15 | might be interested in based on user's interests. 16 | 17 | :copyright: (c) 2017 by Vishwas B Sharma. 18 | :licence: MIT, see LICENSE for more details. 19 | """ 20 | 21 | __title__ = "gitsuggest" 22 | __version__ = "0.0.13" 23 | __author__ = "Vishwas B Sharma" 24 | __author_email__ = "sharma.vishwas88@gmail.com" 25 | __license__ = "MIT" 26 | __copyright__ = "Copyright 2017 Vishwas B Sharma" 27 | 28 | from .suggest import GitSuggest 29 | from .utilities import ReposToHTML 30 | -------------------------------------------------------------------------------- /gitsuggest/commandline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | gitsuggest.commandline 6 | ~~~~~~~~~~~~~~~~~~~~~~ 7 | 8 | This module contains code to use the GitSuggest as commandline. 9 | 10 | Usage: 11 | 12 | >>> gitsuggest --help 13 | usage: gitsuggest [-h] username 14 | 15 | positional arguments: 16 | username Github Username 17 | 18 | optional arguments: 19 | -h, --help show this help message and exit 20 | --deep_dive If added considers repositories starred by users you follow 21 | along with repositories you have starred. Is significantly 22 | slower. 23 | 24 | >>> gitsuggest 25 | # Asks for password input in a secure way to fetch suggested repositories 26 | # for the authenticated user. 27 | """ 28 | 29 | import argparse 30 | import getpass 31 | import webbrowser 32 | 33 | import crayons 34 | from github.GithubException import BadCredentialsException, TwoFactorException 35 | 36 | from .suggest import GitSuggest 37 | from .utilities import ReposToHTML 38 | 39 | 40 | def main(): 41 | """Starting point for the program execution.""" 42 | 43 | # Create command line parser. 44 | parser = argparse.ArgumentParser() 45 | 46 | # Adding command line arguments. 47 | parser.add_argument("username", help="Github Username", default=None) 48 | 49 | parser.add_argument( 50 | "--deep_dive", 51 | help=" ".join( 52 | [ 53 | "If added considers repositories starred by users", 54 | "you follow along with repositories you have", 55 | "starred. Is significantly slower.", 56 | ] 57 | ), 58 | action="store_true", 59 | default=False, 60 | ) 61 | 62 | # Parse command line arguments. 63 | arguments = parser.parse_args() 64 | 65 | if arguments.username is None: 66 | parser.print_help() 67 | return 68 | 69 | print("") 70 | print( 71 | crayons.white( 72 | "Authentication (with password) have higher rate limits." 73 | ) 74 | ) 75 | print( 76 | crayons.white( 77 | "Skipping password might cause failure due to rate limit." 78 | ) 79 | ) 80 | print("") 81 | 82 | password = getpass.getpass( 83 | crayons.blue( 84 | "Enter password (to skip press enter without entering anything): ", 85 | bold=True, 86 | ) 87 | ) 88 | 89 | try: 90 | gs = GitSuggest( 91 | username=arguments.username, 92 | password=password, 93 | token=None, 94 | deep_dive=arguments.deep_dive, 95 | ) 96 | except BadCredentialsException: 97 | print("") 98 | print( 99 | crayons.red( 100 | "Incorrect password provided, to skip password enter nothing.", 101 | bold=True, 102 | ) 103 | ) 104 | exit() 105 | except TwoFactorException: 106 | print("") 107 | print( 108 | crayons.red( 109 | "\n".join( 110 | [ 111 | "You have 2FA set up, please enter a personal access token.", 112 | "You can generate one on https://github.com/settings/tokens", 113 | ] 114 | ), 115 | bold=True, 116 | ) 117 | ) 118 | exit() 119 | 120 | print("") 121 | print(crayons.green("Suggestions generated!")) 122 | 123 | file_name = "/tmp/gitresults.html" 124 | repos = list(gs.get_suggested_repositories()) 125 | 126 | r2h = ReposToHTML(arguments.username, repos) 127 | r2h.to_html(file_name) 128 | 129 | webbrowser.open_new("file://" + file_name) 130 | 131 | 132 | if __name__ == "__main__": 133 | main() 134 | -------------------------------------------------------------------------------- /gitsuggest/gitlang/languages.txt: -------------------------------------------------------------------------------- 1 | 1c-enterprise 2 | abap 3 | abnf 4 | actionscript 5 | ada 6 | agda 7 | ags-script 8 | alloy 9 | alpine-abuild 10 | ampl 11 | ant-build-system 12 | antlr 13 | apacheconf 14 | apex 15 | api-blueprint 16 | apl 17 | apollo-guidance-computer 18 | applescript 19 | arc 20 | arduino 21 | asciidoc 22 | asn.1 23 | asp 24 | aspectj 25 | assembly 26 | ats 27 | augeas 28 | autohotkey 29 | autoit 30 | awk 31 | batchfile 32 | befunge 33 | bison 34 | bitbake 35 | blade 36 | blitzbasic 37 | blitzmax 38 | bluespec 39 | boo 40 | brainfuck 41 | brightscript 42 | bro 43 | c 44 | c# 45 | c++ 46 | c-objdump 47 | c2hs-haskell 48 | cap'n-proto 49 | cartocss 50 | ceylon 51 | chapel 52 | charity 53 | chuck 54 | cirru 55 | clarion 56 | clean 57 | click 58 | clips 59 | clojure 60 | cmake 61 | cobol 62 | coffeescript 63 | coldfusion 64 | coldfusion-cfc 65 | collada 66 | common-lisp 67 | component-pascal 68 | cool 69 | coq 70 | cpp-objdump 71 | creole 72 | crystal 73 | cson 74 | csound 75 | csound-document 76 | csound-score 77 | css 78 | csv 79 | gherkin 80 | cuda 81 | cycript 82 | cython 83 | d 84 | d-objdump 85 | darcs-patch 86 | dart 87 | desktop 88 | diff 89 | digital-command-language 90 | dm 91 | dns-zone 92 | dockerfile 93 | dogescript 94 | dtrace 95 | dylan 96 | e 97 | eagle 98 | ebnf 99 | ec 100 | ecere-projects 101 | ecl 102 | eclipse 103 | edn 104 | eiffel 105 | ejs 106 | elixir 107 | elm 108 | emacs-lisp 109 | emberscript 110 | eq 111 | erlang 112 | f# 113 | factor 114 | fancy 115 | fantom 116 | filebench-wml 117 | filterscript 118 | fish 119 | flux 120 | formatted 121 | forth 122 | fortran 123 | freemarker 124 | frege 125 | g-code 126 | game-maker-language 127 | gams 128 | gap 129 | gcc-machine-description 130 | gdb 131 | gdscript 132 | genie 133 | genshi 134 | gentoo-ebuild 135 | gentoo-eclass 136 | gettext-catalog 137 | gherkin 138 | glsl 139 | glyph 140 | gn 141 | gnuplot 142 | go 143 | golo 144 | gosu 145 | grace 146 | gradle 147 | grammatical-framework 148 | graph-modeling-language 149 | graphql 150 | graphviz-(dot) 151 | groovy 152 | groovy-server-pages 153 | hack 154 | haml 155 | handlebars 156 | harbour 157 | haskell 158 | haxe 159 | hcl 160 | hlsl 161 | html 162 | html+django 163 | html+ecr 164 | html+eex 165 | html+erb 166 | html+php 167 | http 168 | hy 169 | hyphy 170 | idl 171 | idris 172 | igor-pro 173 | inform-7 174 | ini 175 | inno-setup 176 | io 177 | ioke 178 | irc-log 179 | isabelle 180 | isabelle-root 181 | j 182 | jasmin 183 | java 184 | java-server-pages 185 | javascript 186 | jflex 187 | json 188 | json5 189 | jsoniq 190 | jsonld 191 | jsx 192 | julia 193 | jupyter-notebook 194 | kicad 195 | kit 196 | kotlin 197 | krl 198 | labview 199 | lasso 200 | latte 201 | lean 202 | less 203 | lex 204 | lfe 205 | lilypond 206 | limbo 207 | linker-script 208 | linux-kernel-module 209 | liquid 210 | literate-agda 211 | literate-coffeescript 212 | literate-haskell 213 | livescript 214 | llvm 215 | logos 216 | logtalk 217 | lolcode 218 | lookml 219 | loomscript 220 | lsl 221 | lua 222 | m 223 | m4 224 | m4sugar 225 | makefile 226 | mako 227 | markdown 228 | mask 229 | mathematica 230 | matlab 231 | maven-pom 232 | max 233 | maxscript 234 | mediawiki 235 | mercury 236 | meson 237 | metal 238 | minid 239 | mirah 240 | modelica 241 | modula-2 242 | module-management-system 243 | monkey 244 | moocode 245 | moonscript 246 | mql4 247 | mql5 248 | mtml 249 | muf 250 | mupad 251 | myghty 252 | ncl 253 | nemerle 254 | nesc 255 | netlinx 256 | netlinx+erb 257 | netlogo 258 | newlisp 259 | nginx 260 | nim 261 | ninja 262 | nit 263 | nix 264 | nl 265 | nsis 266 | nu 267 | numpy 268 | objdump 269 | objective-c 270 | objective-c++ 271 | objective-j 272 | ocaml 273 | omgrofl 274 | ooc 275 | opa 276 | opal 277 | opencl 278 | openedge-abl 279 | openrc-runscript 280 | openscad 281 | opentype-feature-file 282 | org 283 | ox 284 | oxygene 285 | oz 286 | p4 287 | pan 288 | papyrus 289 | parrot 290 | parrot-assembly 291 | parrot-internal-representation 292 | pascal 293 | pawn 294 | perl 295 | perl6 296 | php 297 | pic 298 | pickle 299 | picolisp 300 | piglatin 301 | pike 302 | plpgsql 303 | plsql 304 | pod 305 | pogoscript 306 | pony 307 | postscript 308 | pov-ray-sdl 309 | powerbuilder 310 | powershell 311 | processing 312 | prolog 313 | propeller-spin 314 | protocol-buffer 315 | public-key 316 | pug 317 | puppet 318 | pure-data 319 | purebasic 320 | purescript 321 | python 322 | python-console 323 | python-traceback 324 | qmake 325 | qml 326 | r 327 | racket 328 | ragel 329 | raml 330 | rascal 331 | raw-token-data 332 | rdoc 333 | realbasic 334 | reason 335 | rebol 336 | red 337 | redcode 338 | ren'py 339 | renderscript 340 | restructuredtext 341 | rexx 342 | rhtml 343 | rmarkdown 344 | robotframework 345 | roff 346 | rouge 347 | rpm-spec 348 | ruby 349 | runoff 350 | rust 351 | sage 352 | saltstack 353 | sas 354 | sass 355 | scala 356 | scaml 357 | scheme 358 | scilab 359 | scss 360 | self 361 | shell 362 | shellsession 363 | shen 364 | slash 365 | slim 366 | smali 367 | smalltalk 368 | smarty 369 | smt 370 | sourcepawn 371 | sparql 372 | spline-font-database 373 | sqf 374 | sql 375 | sqlpl 376 | squirrel 377 | srecode-template 378 | stan 379 | standard-ml 380 | stata 381 | ston 382 | stylus 383 | sublime-text-config 384 | subrip-text 385 | supercollider 386 | svg 387 | swift 388 | systemverilog 389 | tcl 390 | tcsh 391 | tea 392 | terra 393 | tex 394 | text 395 | textile 396 | thrift 397 | ti-program 398 | tla 399 | toml 400 | turing 401 | turtle 402 | twig 403 | txl 404 | typescript 405 | unified-parallel-c 406 | unity3d-asset 407 | unix-assembly 408 | uno 409 | unrealscript 410 | urweb 411 | vala 412 | vcl 413 | verilog 414 | vhdl 415 | vim-script 416 | vim-script 417 | visual-basic 418 | volt 419 | vue 420 | wavefront-material 421 | wavefront-object 422 | web-ontology-language 423 | webidl 424 | wisp 425 | world-of-warcraft-addon-data 426 | x10 427 | xbase 428 | xc 429 | xcompose 430 | xml 431 | xojo 432 | xpages 433 | xproc 434 | xquery 435 | xs 436 | xslt 437 | xtend 438 | yacc 439 | yaml 440 | yang 441 | zephir 442 | zimpl 443 | -------------------------------------------------------------------------------- /gitsuggest/gitlang/others.txt: -------------------------------------------------------------------------------- 1 | js 2 | node 3 | nodejs -------------------------------------------------------------------------------- /gitsuggest/res/base.htm.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 12 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Site logo 40 |
41 |
42 |
43 |

gitSuggest beta

44 |

An experimental tool which uses LDA to suggest github repositories 45 | based on the repositories you have shown interest in. 46 |

47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |

gitSuggest

57 |
58 | Star 65 | Fork 72 |
73 |
74 |
75 |
76 |

Contribute

77 |
78 | Via Paypal 82 |
83 |
84 |
85 |
86 |

Stay connected

87 | 105 |
106 |
107 |
108 | {% block content %}{% endblock %} 109 |
110 |
111 |
112 | 113 | 116 | 117 | 118 | 119 | 123 | 127 | 131 | 132 | -------------------------------------------------------------------------------- /gitsuggest/res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csurfer/gitsuggest/5d98ecef670ed81edd3f9b16f7f286f786bcd7a5/gitsuggest/res/logo.png -------------------------------------------------------------------------------- /gitsuggest/res/page.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 15 | 39 | 40 | 41 |
42 |
43 |

Repositories suggested by csurfer/gitsuggest

44 |
45 | $results 46 |
47 | 48 | -------------------------------------------------------------------------------- /gitsuggest/res/result.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 |
8 |
9 |

10 | $title 11 |

12 |

$description

13 |
14 | 15 |
16 |
-------------------------------------------------------------------------------- /gitsuggest/res/suggest.htm.j2: -------------------------------------------------------------------------------- 1 | {% extends "base.htm.j2" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 |

Welcome @{{ user_login }}!

8 |
9 |

Based on the repositories you have starred, we think you might like these repositories too...

10 |
11 |
12 | 13 | {% for repo in repos %} 14 |
15 |
16 |
17 |

18 | {{ repo.full_name }} 19 |

20 |

{{ repo.language }} (★ {{ repo.stargazers_count }}) 21 |

{{ repo.description }}

22 |
23 |
24 | 25 | {% endfor %} 26 | 27 | {% endblock %} -------------------------------------------------------------------------------- /gitsuggest/suggest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | gitsuggest.suggest 5 | ~~~~~~~~~~~~~~~~~~ 6 | 7 | This module contains the primary objects that power GitSuggest. 8 | """ 9 | 10 | import itertools 11 | from collections import defaultdict 12 | from operator import attrgetter 13 | from os import path 14 | 15 | import github 16 | from gensim import corpora, models 17 | from nltk.corpus import words, stopwords 18 | from nltk.tokenize import RegexpTokenizer 19 | 20 | 21 | class GitSuggest(object): 22 | """Class to suggest git repositories for a user.""" 23 | 24 | # Length of description of a repository over which it is a high chance 25 | # that it is a spammy repository. 26 | MAX_DESC_LEN = 300 27 | 28 | def __init__( 29 | self, username=None, password=None, token=None, deep_dive=False 30 | ): 31 | """Constructor. 32 | 33 | Username and password is used to get an authenticated handle which has 34 | a higher rate limit when compared to unauthenticated handle which will 35 | have much lesser rate limit. 36 | 37 | :param username: Github username. 38 | :param password: Github password. 39 | :param token: Github access token. 40 | :param deep_dive: When set to True considers the repositories people 41 | you follow have starred along with the ones you have 42 | starred. 43 | """ 44 | if token: 45 | self.github = github.Github(token) 46 | username = self.github.get_user().login 47 | assert username is not None, "Invalid token" 48 | else: 49 | assert username is not None, "Suggest cannot work without username" 50 | # Github handle. 51 | if password is not None and password != "": 52 | self.github = github.Github(username, password) 53 | else: 54 | self.github = github.Github() 55 | 56 | self.deep_dive = deep_dive 57 | 58 | # Populate repositories to be used for generating suggestions. 59 | self.user_starred_repositories = list() 60 | self.user_following_starred_repositories = list() 61 | self.__populate_repositories_of_interest(username) 62 | 63 | # Construct LDA model. 64 | self.lda_model = None 65 | self.__construct_lda_model() 66 | 67 | # Suggested repository set. 68 | self.suggested_repositories = None 69 | 70 | # Search for repositories is the costliest operation so defer it as 71 | # much as possible. 72 | 73 | @staticmethod 74 | def get_unique_repositories(repo_list): 75 | """Method to create unique list of repositories from the list of 76 | repositories given. 77 | 78 | :param repo_list: List of repositories which might contain duplicates. 79 | :return: List of repositories with no duplicate in them. 80 | """ 81 | unique_list = list() 82 | included = defaultdict(lambda: False) 83 | for repo in repo_list: 84 | if not included[repo.full_name]: 85 | unique_list.append(repo) 86 | included[repo.full_name] = True 87 | return unique_list 88 | 89 | @staticmethod 90 | def minus(repo_list_a, repo_list_b): 91 | """Method to create a list of repositories such that the repository 92 | belongs to repo list a but not repo list b. 93 | 94 | In an ideal scenario we should be able to do this by set(a) - set(b) 95 | but as GithubRepositories have shown that set() on them is not reliable 96 | resort to this until it is all sorted out. 97 | 98 | :param repo_list_a: List of repositories. 99 | :param repo_list_b: List of repositories. 100 | """ 101 | included = defaultdict(lambda: False) 102 | 103 | for repo in repo_list_b: 104 | included[repo.full_name] = True 105 | 106 | a_minus_b = list() 107 | for repo in repo_list_a: 108 | if not included[repo.full_name]: 109 | included[repo.full_name] = True 110 | a_minus_b.append(repo) 111 | 112 | return a_minus_b 113 | 114 | def __populate_repositories_of_interest(self, username): 115 | """Method to populate repositories which will be used to suggest 116 | repositories for the user. For this purpose we use two kinds of 117 | repositories. 118 | 119 | 1. Repositories starred by user him/herself. 120 | 2. Repositories starred by the users followed by the user. 121 | 122 | :param username: Username for the user for whom repositories are being 123 | suggested for. 124 | """ 125 | # Handle to the user to whom repositories need to be suggested. 126 | user = self.github.get_user(username) 127 | 128 | # Procure repositories starred by the user. 129 | self.user_starred_repositories.extend(user.get_starred()) 130 | 131 | # Repositories starred by users followed by the user. 132 | if self.deep_dive: 133 | for following_user in user.get_following(): 134 | self.user_following_starred_repositories.extend( 135 | following_user.get_starred() 136 | ) 137 | 138 | def __get_interests(self): 139 | """Method to procure description of repositories the authenticated user 140 | is interested in. 141 | 142 | We currently attribute interest to: 143 | 1. The repositories the authenticated user has starred. 144 | 2. The repositories the users the authenticated user follows have 145 | starred. 146 | 147 | :return: List of repository descriptions. 148 | """ 149 | # All repositories of interest. 150 | repos_of_interest = itertools.chain( 151 | self.user_starred_repositories, 152 | self.user_following_starred_repositories, 153 | ) 154 | 155 | # Extract descriptions out of repositories of interest. 156 | repo_descriptions = [repo.description for repo in repos_of_interest] 157 | return list(set(repo_descriptions)) 158 | 159 | def __get_words_to_ignore(self): 160 | """Compiles list of all words to ignore. 161 | 162 | :return: List of words to ignore. 163 | """ 164 | # Stop words in English. 165 | english_stopwords = stopwords.words("english") 166 | 167 | here = path.abspath(path.dirname(__file__)) 168 | 169 | # Languages in git repositories. 170 | git_languages = [] 171 | with open(path.join(here, "gitlang/languages.txt"), "r") as langauges: 172 | git_languages = [line.strip() for line in langauges] 173 | 174 | # Other words to avoid in git repositories. 175 | words_to_avoid = [] 176 | with open(path.join(here, "gitlang/others.txt"), "r") as languages: 177 | words_to_avoid = [line.strip() for line in languages] 178 | 179 | return set( 180 | itertools.chain(english_stopwords, git_languages, words_to_avoid) 181 | ) 182 | 183 | def __get_words_to_consider(self): 184 | """Compiles list of all words to consider. 185 | 186 | :return: List of words to consider. 187 | """ 188 | return set(words.words()) 189 | 190 | def __clean_and_tokenize(self, doc_list): 191 | """Method to clean and tokenize the document list. 192 | 193 | :param doc_list: Document list to clean and tokenize. 194 | :return: Cleaned and tokenized document list. 195 | """ 196 | # Some repositories fill entire documentation in description. We ignore 197 | # such repositories for cleaner tokens. 198 | doc_list = filter( 199 | lambda x: x is not None and len(x) <= GitSuggest.MAX_DESC_LEN, 200 | doc_list, 201 | ) 202 | 203 | cleaned_doc_list = list() 204 | 205 | # Regular expression to remove out all punctuations, numbers and other 206 | # un-necessary text substrings like emojis etc. 207 | tokenizer = RegexpTokenizer(r"[a-zA-Z]+") 208 | 209 | # Get stop words. 210 | stopwords = self.__get_words_to_ignore() 211 | 212 | # Get english words. 213 | dict_words = self.__get_words_to_consider() 214 | 215 | for doc in doc_list: 216 | # Lowercase doc. 217 | lower = doc.lower() 218 | 219 | # Tokenize removing numbers and punctuation. 220 | tokens = tokenizer.tokenize(lower) 221 | 222 | # Include meaningful words. 223 | tokens = [tok for tok in tokens if tok in dict_words] 224 | 225 | # Remove stopwords. 226 | tokens = [tok for tok in tokens if tok not in stopwords] 227 | 228 | # Filter Nones if any are introduced. 229 | tokens = [tok for tok in tokens if tok is not None] 230 | 231 | cleaned_doc_list.append(tokens) 232 | 233 | return cleaned_doc_list 234 | 235 | def __construct_lda_model(self): 236 | """Method to create LDA model to procure list of topics from. 237 | 238 | We do that by first fetching the descriptions of repositories user has 239 | shown interest in. We tokenize the hence fetched descriptions to 240 | procure list of cleaned tokens by dropping all the stop words and 241 | language names from it. 242 | 243 | We use the cleaned and sanitized token list to train LDA model from 244 | which we hope to procure topics of interests to the authenticated user. 245 | """ 246 | # Fetch descriptions of repos of interest to authenticated user. 247 | repos_of_interest = self.__get_interests() 248 | 249 | # Procure clean tokens from the descriptions. 250 | cleaned_tokens = self.__clean_and_tokenize(repos_of_interest) 251 | 252 | # If cleaned tokens are empty, it can cause an exception while 253 | # generating LDA. But tokens shouldn't be something meaningful as that 254 | # would mean we are suggesting repos without reason. Hence the random 255 | # string to ensure that LDA doesn't cause exception but the token 256 | # doesn't generate any suggestions either. 257 | if not cleaned_tokens: 258 | cleaned_tokens = [["zkfgzkfgzkfgzkfgzkfgzkfg"]] 259 | 260 | # Setup LDA requisites. 261 | dictionary = corpora.Dictionary(cleaned_tokens) 262 | corpus = [dictionary.doc2bow(text) for text in cleaned_tokens] 263 | 264 | # Generate LDA model 265 | self.lda_model = models.ldamodel.LdaModel( 266 | corpus, num_topics=1, id2word=dictionary, passes=10 267 | ) 268 | 269 | def __get_query_for_repos(self, term_count=5): 270 | """Method to procure query based on topics authenticated user is 271 | interested in. 272 | 273 | :param term_count: Count of terms in query. 274 | :return: Query string. 275 | """ 276 | repo_query_terms = list() 277 | for term in self.lda_model.get_topic_terms(0, topn=term_count): 278 | repo_query_terms.append(self.lda_model.id2word[term[0]]) 279 | return " ".join(repo_query_terms) 280 | 281 | def __get_repos_for_query(self, query): 282 | """Method to procure git repositories for the query provided. 283 | 284 | IMPORTANT NOTE: This is the costliest of all the calls hence keep this 285 | to a minimum. 286 | 287 | :param query: String representing the repositories intend to search. 288 | :return: Iterator for repositories found using the query. 289 | """ 290 | return self.github.search_repositories( 291 | query, "stars", "desc" 292 | ).get_page( 293 | 0 294 | ) 295 | 296 | def get_suggested_repositories(self): 297 | """Method to procure suggested repositories for the user. 298 | 299 | :return: Iterator to procure suggested repositories for the user. 300 | """ 301 | if self.suggested_repositories is None: 302 | # Procure repositories to suggest to user. 303 | repository_set = list() 304 | for term_count in range(5, 2, -1): 305 | query = self.__get_query_for_repos(term_count=term_count) 306 | repository_set.extend(self.__get_repos_for_query(query)) 307 | 308 | # Remove repositories authenticated user is already interested in. 309 | catchy_repos = GitSuggest.minus( 310 | repository_set, self.user_starred_repositories 311 | ) 312 | 313 | # Filter out repositories with too long descriptions. This is a 314 | # measure to weed out spammy repositories. 315 | filtered_repos = [] 316 | 317 | if len(catchy_repos) > 0: 318 | for repo in catchy_repos: 319 | if ( 320 | repo is not None 321 | and repo.description is not None 322 | and len(repo.description) <= GitSuggest.MAX_DESC_LEN 323 | ): 324 | filtered_repos.append(repo) 325 | 326 | # Present the repositories, highly starred to not starred. 327 | filtered_repos = sorted( 328 | filtered_repos, 329 | key=attrgetter("stargazers_count"), 330 | reverse=True, 331 | ) 332 | 333 | self.suggested_repositories = GitSuggest.get_unique_repositories( 334 | filtered_repos 335 | ) 336 | 337 | # Return an iterator to help user fetch the repository listing. 338 | for repository in self.suggested_repositories: 339 | yield repository 340 | -------------------------------------------------------------------------------- /gitsuggest/utilities.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | gitsuggest.utilities 5 | ~~~~~~~~~~~~~~~~~~~~ 6 | 7 | This module contains utility classes which help in displaying the results. 8 | """ 9 | 10 | from os import path 11 | from jinja2 import FileSystemLoader, Environment 12 | 13 | 14 | class ReposToHTML(object): 15 | """Class to convert the repository list to HTML page with results.""" 16 | 17 | def __init__(self, user, repos): 18 | """Constructor. 19 | 20 | :param user: User for whom we are fetching the repositories for. 21 | :param repos: List of github.Repository objects. 22 | """ 23 | self.user = user 24 | self.repos = repos 25 | 26 | def get_html(self): 27 | """Method to convert the repository list to a search results page.""" 28 | here = path.abspath(path.dirname(__file__)) 29 | 30 | env = Environment(loader=FileSystemLoader(path.join(here, "res/"))) 31 | suggest = env.get_template("suggest.htm.j2") 32 | 33 | return suggest.render( 34 | logo=path.join(here, "res/logo.png"), 35 | user_login=self.user, 36 | repos=self.repos, 37 | ) 38 | 39 | def to_html(self, write_to): 40 | """Method to convert the repository list to a search results page and 41 | write it to a HTML file. 42 | 43 | :param write_to: File/Path to write the html file to. 44 | """ 45 | page_html = self.get_html() 46 | 47 | with open(write_to, "wb") as writefile: 48 | writefile.write(page_html.encode("utf-8")) 49 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3. 3 | universal=1 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | from setuptools.command.develop import develop 4 | from setuptools.command.install import install 5 | 6 | from os import path 7 | 8 | here = path.abspath(path.dirname(__file__)) 9 | 10 | # Get the long description from the README file 11 | with open(path.join(here, "README.rst")) as f: 12 | long_description = f.read() 13 | 14 | 15 | def _post_install(): 16 | """Post installation nltk corpus downloads.""" 17 | import nltk 18 | 19 | nltk.download("words") 20 | nltk.download("stopwords") 21 | 22 | 23 | class PostDevelop(develop): 24 | """Post-installation for development mode.""" 25 | 26 | def run(self): 27 | develop.run(self) 28 | self.execute(_post_install, [], msg="Running post installation tasks") 29 | 30 | 31 | class PostInstall(install): 32 | """Post-installation for production mode.""" 33 | 34 | def run(self): 35 | install.run(self) 36 | self.execute(_post_install, [], msg="Running post installation tasks") 37 | 38 | 39 | setup( 40 | # Name of the module 41 | name="gitsuggest", 42 | # Details 43 | version="0.0.13", 44 | description="A tool to suggest github repositories based on the" 45 | + " repositories you have shown interest in.", 46 | long_description=long_description, 47 | # The project's main homepage. 48 | url="https://github.com/csurfer/gitsuggest", 49 | # Author details 50 | author="Vishwas B Sharma", 51 | author_email="sharma.vishwas88@gmail.com", 52 | # License 53 | license="MIT", 54 | packages=["gitsuggest"], 55 | # NOTE: Package data to be included both in MANIFEST.in and here for sdist 56 | # to consider it to put in package (MANIFEST) and for setuptools to copy 57 | # it over (package_data). 58 | package_dir={"gitsuggest": "gitsuggest"}, 59 | package_data={"gitsuggest": ["res/*", "gitlang/*"]}, 60 | entry_points={ 61 | "console_scripts": ["gitsuggest=gitsuggest.commandline:main"] 62 | }, 63 | test_suite="tests", 64 | keywords="github repository suggestion", 65 | classifiers=[ 66 | # Intended Audience. 67 | "Intended Audience :: Developers", 68 | "Intended Audience :: Education", 69 | # License. 70 | "License :: OSI Approved :: MIT License", 71 | # Project maturity. 72 | "Development Status :: 3 - Alpha", 73 | # Operating Systems. 74 | "Operating System :: POSIX", 75 | # Supported Languages. 76 | "Programming Language :: Python :: 2.7", 77 | "Programming Language :: Python :: 3.4", 78 | "Programming Language :: Python :: 3.5", 79 | "Programming Language :: Python :: 3.6", 80 | # Topic tags. 81 | "Topic :: Software Development :: Libraries :: Python Modules", 82 | ], 83 | install_requires=["gensim", "PyGithub", "nltk", "crayons", "jinja2"], 84 | cmdclass={"develop": PostDevelop, "install": PostInstall}, 85 | ) 86 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csurfer/gitsuggest/5d98ecef670ed81edd3f9b16f7f286f786bcd7a5/tests/__init__.py -------------------------------------------------------------------------------- /tests/mockentities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | """File with mock classes.""" 5 | 6 | 7 | class MockRepo(object): 8 | """MockClass to represent a GitRepository.""" 9 | 10 | def __init__(self, full_name, description): 11 | """Constructor. 12 | 13 | :param full_name: Name of the repository. 14 | :param description: Description of the repository. 15 | """ 16 | self.full_name = full_name 17 | self.description = description 18 | 19 | def __eq__(self, other): 20 | return self.full_name == other.full_name and self.description == other.description 21 | -------------------------------------------------------------------------------- /tests/test_suggest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | gitsuggest.suggest test 6 | ~~~~~~~~~~ 7 | 8 | Usage from git root: 9 | 10 | >>> python setup.py test 11 | """ 12 | 13 | import unittest 14 | 15 | from gitsuggest import GitSuggest 16 | 17 | from .mockentities import MockRepo 18 | 19 | 20 | class GitSuggestTest(unittest.TestCase): 21 | """Class to test :class:`GitSuggest` functionality.""" 22 | 23 | def test_get_unique_repositories(self): 24 | """Tests to validate get_unique_repositories().""" 25 | 26 | repo_list = [ 27 | MockRepo("userA/proA", "A Desc"), 28 | MockRepo("userB/proB", "B Desc"), 29 | MockRepo("userA/proA", "A Desc"), 30 | MockRepo("userB/proB", "B Desc"), 31 | MockRepo("userC/proC", "C Desc"), 32 | ] 33 | expected_unique = [ 34 | MockRepo("userA/proA", "A Desc"), 35 | MockRepo("userB/proB", "B Desc"), 36 | MockRepo("userC/proC", "C Desc"), 37 | ] 38 | 39 | unique = GitSuggest.get_unique_repositories(repo_list) 40 | 41 | self.assertEqual(len(expected_unique), len(unique)) 42 | self.assertEqual(expected_unique, unique) 43 | 44 | def test_minus(self): 45 | """Tests to validate minus().""" 46 | 47 | repo_list_a = [ 48 | MockRepo("userA/proA", "A Desc"), 49 | MockRepo("userB/proB", "B Desc"), 50 | MockRepo("userC/proC", "C Desc"), 51 | MockRepo("userD/proD", "D Desc"), 52 | ] 53 | repo_list_b = [ 54 | MockRepo("userB/proB", "B Desc"), MockRepo("userD/proD", "D Desc") 55 | ] 56 | expected_a_minus_b = [ 57 | MockRepo("userA/proA", "A Desc"), MockRepo("userC/proC", "C Desc") 58 | ] 59 | 60 | a_minus_b = GitSuggest.minus(repo_list_a, repo_list_b) 61 | 62 | self.assertEqual(len(expected_a_minus_b), len(a_minus_b)) 63 | self.assertEqual(expected_a_minus_b, a_minus_b) 64 | 65 | 66 | if __name__ == "__main__": 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /tests/test_utilities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | gitsuggest.utilities test 6 | ~~~~~~~~~~ 7 | 8 | Usage from git root: 9 | 10 | >>> python setup.py test 11 | """ 12 | 13 | import unittest 14 | 15 | from gitsuggest import ReposToHTML 16 | 17 | from .mockentities import MockRepo 18 | 19 | 20 | class ReposToHTMLTest(unittest.TestCase): 21 | """Class to test :class:`ReposToHTML` functionality.""" 22 | 23 | def test_get_html(self): 24 | """Tests convertion of repository object list to HTML page.""" 25 | 26 | repo_list = [ 27 | MockRepo("userA/proA", "A Desc"), 28 | MockRepo("userB/proB", "B Desc"), 29 | MockRepo("userC/proC", "C Desc"), 30 | ] 31 | 32 | r2h = ReposToHTML(repo_list) 33 | page = r2h.get_html() 34 | 35 | # Assert structure of HTML page. 36 | self.assertTrue(page.startswith("")) 37 | self.assertTrue(page.endswith("")) 38 | self.assertEqual(page.count("section class"), len(repo_list)) 39 | 40 | # Assert contents of HTML page. 41 | for repo in repo_list: 42 | title = repo.full_name 43 | description = repo.description 44 | link = ReposToHTML.GITHUB_URL + title 45 | self.assertEqual(page.count(title), 1 + 2) # Title + Links 46 | self.assertEqual(page.count(description), 1) # Description 47 | self.assertEqual(page.count(link), 2) # Links 48 | 49 | 50 | if __name__ == "__main__": 51 | unittest.main() 52 | --------------------------------------------------------------------------------