.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Scout
2 |
3 |
4 |
5 | [](./STARS)
6 | [](./FORKS)
7 | [](./CONTRIBUTORS)
8 |
9 |
10 | ⭐ Find hacktoberfest repos from your terminal by getting repos by the range of stars they have
11 |
12 |
13 | Have you noticed that often the best repos to contribute to have somewhere between 5-1000 stars? To find these repos on GitHub however, is quite hard! With scout, this issue ends.
14 |
15 | You can search repos with the exact amount of stars you wish as well as your language and additional optional keywords.
16 |
17 | ## Install
18 |
19 | ```
20 | pip install gh-scout
21 | ```
22 |
23 | Before running `scout`:
24 | 1. Create a [GitHub token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), no extra perms are required for the token.
25 | 1. You can name the token whatever you want, for example: "Scout Token"
26 | 2. You can use tokens that you may already have, and it will work just fine
27 | 2. Create an env variable called `SCOUT_TOKEN`, this will be used to ensure you don't get rate limited by GitHub.
28 | 1. On windows, simply type in command prompt ```setx SCOUT_TOKEN ```
29 | 2. On MacOs and Linux, refer [this guide](https://github.com/sindresorhus/guides/blob/main/set-environment-variables.md) on how to set environment variables. In the tutorial, fill ```FOO``` with "SCOUT_TOKEN" and ```bar``` with the generated token from Github.
30 | 3. Remember to refresh your terminal to load the new env variable
31 | 4. Now you're good to go! Run `scout` to start!
32 | 1. On windows, press win + r, type in `scout`, and click Ok.
33 |
34 | 
35 |
36 | ## Show Forks Option
37 | ```bash
38 | scout --forks
39 | ```
40 | 
41 |
42 | ## Contributors ❤️
43 |
44 |
45 | Thanks to these amazing people
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | certifi==2022.9.24
2 | charset-normalizer==2.1.1
3 | commonmark==0.9.1
4 | idna==3.4
5 | Pygments==2.13.0
6 | requests==2.28.1
7 | rich==12.6.0
8 | urllib3==1.26.12
9 |
--------------------------------------------------------------------------------
/scout/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechWiz-3/scout/fe29dc1cc81bbc6b2d878469c6bdb5dcf9b04ff3/scout/__init__.py
--------------------------------------------------------------------------------
/scout/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from typing import Dict
3 |
4 | import requests
5 | import random
6 | import os
7 | import time
8 | import re
9 | import sys
10 | from datetime import datetime as dt
11 |
12 | from contextlib import contextmanager
13 | import argparse
14 | from rich.live import Live
15 | from rich.console import Console
16 | from rich.rule import Rule
17 | from rich.table import Table
18 | from rich.markdown import Markdown
19 |
20 | parser = argparse.ArgumentParser()
21 | parser.add_argument(
22 | '--standard', action='store_true', required=False,
23 | help='run the standard query for Python repos under 1k stars'
24 | )
25 |
26 | parser.add_argument(
27 | '--forks', action='store_true', required=False,
28 | help='show repos Forks'
29 | )
30 |
31 | parser.add_argument(
32 | '--nocolor', action='store_true', required=False,
33 | help='colors ommitted from output'
34 | )
35 |
36 | args = parser.parse_args()
37 | no_color = args.nocolor
38 | console = Console(no_color=no_color)
39 |
40 | TOKEN = os.getenv("SCOUT_TOKEN")
41 |
42 | # BASE_URL = "https://api.github.com/search/repositories?q=stars:%3E={}%20language:{}%20topic:hacktoberfest"
43 | BASE_URL = "https://api.github.com/search/repositories?q={}stars:%3C={}%20language:{}%20topic:hacktoberfest"
44 |
45 | def get_headers() -> dict[str, str]:
46 | if not TOKEN:
47 | print("You aren't using a Github token, this may result in ratelimits.")
48 | return {'Authorization': 'token ' + TOKEN if TOKEN else ""}
49 |
50 |
51 | def print_welcome_message() -> None:
52 | rule = Rule(
53 | '[b]Your personal opensource [purple]Scout',
54 | align="center",
55 | style="yellow"
56 | )
57 |
58 | console.print(rule)
59 | print("")
60 |
61 |
62 | def get_url():
63 | if args.standard:
64 | keyword = ''
65 | max_stars = '1000'
66 | lang = 'python'
67 | else:
68 | standard_style = "[purple]"
69 | keyword_style = "[purple]"
70 | star_style = "[blue]"
71 |
72 |
73 | try:
74 | standard = console.input(
75 | f"{standard_style}Shall I use the standard search which gets repos in the 1k stars range? \[y/n]: "
76 | )
77 | lang = console.input("Project language: \[python] ")
78 | keyword = console.input(
79 | f"{keyword_style}You can enter a keyword for the search: \[optional] "
80 | )
81 |
82 | except KeyboardInterrupt:
83 | print('\nFarewell my friend, beware the crickets.\n')
84 | sys.exit(1)
85 |
86 | else:
87 | if standard.lower() in ("y", "yes", ""):
88 | max_stars = 1000
89 | else:
90 | max_stars = int(
91 | console.input(
92 | f"{star_style}Star count range \[5-1000 is ideal]: "
93 | )
94 | )
95 |
96 | if lang == "":
97 | lang = "python"
98 |
99 | if keyword != "":
100 | keyword = f"{keyword} "
101 |
102 | url = BASE_URL.format(keyword, max_stars, lang)
103 | return url
104 |
105 |
106 | def request(url):
107 | page = random.randint(1, 3)
108 | response = requests.get(f"{url}&page={page}", headers=get_headers())
109 | status_code = response.status_code
110 |
111 | response_json = response.json()
112 |
113 | if response_json.get("items") is None:
114 | if status_code == 403:
115 | print("API rate limit exceeded, use the Github token")
116 | elif status_code == 401:
117 | print("GitHub Token invalid!")
118 | console.print(f"\n{str(response_json)}") # print response from gh api
119 | exit()
120 |
121 | return response_json
122 |
123 |
124 | def get_table_data(response: str) -> list:
125 | table_data = []
126 | for project in random.sample(response["items"], min(5, 29)):
127 | topics = "` `".join(project["topics"])
128 | topics = f"`{topics}`"
129 | topics = Markdown(topics, style="dim")
130 | stars = "{:,}".format(project["stargazers_count"])
131 | # stars = f":star: {stars}"
132 | issues = "{:,}".format(project["open_issues_count"])
133 | if args.forks:
134 | forks = project["forks"]
135 | project_time = project["updated_at"]
136 | # day = re.match(time, r"^[0-9]{4}-[0-9]{2}-[0-9]{2}")
137 | project_time = project_time[:10]
138 | project_time = dt.strptime(project_time, "%Y-%m-%d")
139 |
140 | if project_time.date() == dt.today().date():
141 | project_time = "Today"
142 | else:
143 | delta = dt.today().date() - project_time.date()
144 | if delta.days == 1:
145 | project_time = f"{str(delta.days)} day"
146 | else:
147 | project_time = f"{str(delta.days)} days"
148 | project_name = project['full_name']
149 | row = [
150 | project_name, project["description"],
151 | str(stars), str(issues), topics, project_time
152 | ]
153 | if args.forks:
154 | row.insert(4, str(forks))
155 |
156 | table_data.append(
157 | row
158 | )
159 | return table_data
160 |
161 | def create_table(table):
162 | table.add_column("Project", header_style="bold cyan", style="bold cyan")
163 | table.add_column("Description", header_style="bold green", style="italic green")
164 | table.add_column("Stars", header_style="bold yellow", style="yellow")
165 | table.add_column("Issues", header_style="bold grey66", style="grey66")
166 | if args.forks:
167 | table.add_column("Forks", header_style="bold dark_orange", style="dark_orange")
168 | table.add_column("Tags", header_style="bold")
169 | table.add_column("Last updated", header_style="red bold", style="red")
170 |
171 |
172 | def display_table(table_data):
173 | table = Table(padding=(0, 1, 1, 1))
174 | create_table(table)
175 | table.add_row(*table_data[0])
176 | with Live(table, console=console, refresh_per_second=4):
177 | for row in table_data[1:]:
178 | table.add_row(*row)
179 | time.sleep(0.5)
180 |
181 |
182 | def cli() -> None:
183 | print_welcome_message()
184 | url = get_url()
185 | console.clear()
186 | response = request(url)
187 | table_data = get_table_data(response)
188 | display_table(table_data)
189 |
190 |
191 | if __name__ == "__main__":
192 | cli()
193 |
--------------------------------------------------------------------------------
/scout/main_test.py:
--------------------------------------------------------------------------------
1 | import io
2 | import sys
3 | import unittest
4 |
5 | from scout.main import print_welcome_message
6 |
7 |
8 | class TestMain(unittest.TestCase):
9 | def test_welcome_message_prints(self):
10 | # Arrange
11 | capturedOutput = io.StringIO()
12 | sys.stdout = capturedOutput
13 |
14 | # Act
15 | print_welcome_message()
16 |
17 | # Assert
18 | self.assertIsNotNone(capturedOutput.getvalue())
19 |
20 | # Cleanup
21 | sys.stdout = sys.__stdout__
22 |
23 | if __name__ == '__main__':
24 | unittest.main()
25 |
--------------------------------------------------------------------------------
/screenshot/show_forks_option.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechWiz-3/scout/fe29dc1cc81bbc6b2d878469c6bdb5dcf9b04ff3/screenshot/show_forks_option.png
--------------------------------------------------------------------------------
/screenshot/standard_output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechWiz-3/scout/fe29dc1cc81bbc6b2d878469c6bdb5dcf9b04ff3/screenshot/standard_output.png
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | from pathlib import Path
3 |
4 | #project_dir = Path(__file__).parent
5 | #long_description = (project_dir / "README.md").read_text()
6 |
7 |
8 |
9 | def read_file(rel_path: str):
10 | return Path(__file__).parent.joinpath(rel_path).read_text()
11 |
12 |
13 | setup(
14 | name="gh-scout",
15 | url="https://github.com/TechWiz-3/scout",
16 | author="Zac the Wise aka TechWiz-3",
17 | version='0.3.3',
18 | description="⭐ Find hacktoberfest repos to contribute to from your CLI",
19 | long_description_content_type='text/markdown',
20 | long_description=read_file("README.md"),
21 | packages=find_packages(),
22 | entry_points='''
23 | [console_scripts]
24 | scout=scout.main:cli
25 | ''',
26 | instal_requires=["rich"],
27 | )
28 |
--------------------------------------------------------------------------------