diff --git a/.github/PULL_REQUEST_TEMPLATE/new_feature.md b/.github/PULL_REQUEST_TEMPLATE/new_feature.md new file mode 100644 index 00000000..2c605d59 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/new_feature.md @@ -0,0 +1,9 @@ +## This PR adds... + +*List your features here and their reasons for creation.* + +## Notes + +*List anything note-worthy here (potential issues, this needs merge to `master` before working, etc....).* + +*Don't forget to link any issues that this PR will solved.* diff --git a/.github/PULL_REQUEST_TEMPLATE/new_icon.md b/.github/PULL_REQUEST_TEMPLATE/new_icon.md new file mode 100644 index 00000000..cffee5f2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/new_icon.md @@ -0,0 +1,9 @@ +**Double check these details before you open a PR** + +- [] PR does not match another non-stale PR currently opened +- [] PR name matches the format *new icon: Icon name (versions separated by comma)* as seen [here](https://github.com/devicons/devicon/blob/develop/CONTRIBUTING.md#overview) +- [] Your icons are put in a folder as seen [here](https://github.com/devicons/devicon/blob/develop/CONTRIBUTING.md#organizational-guidelines) +- [] SVG matches the standards laid out [here](https://github.com/devicons/devicon/blob/develop/CONTRIBUTING.md#svgStandards) +- [] A new object is added in the `devicon.json` file as seen [here](https://github.com/devicons/devicon/blob/develop/CONTRIBUTING.md#-updating-the-deviconjson-) + +Refer to the [`CONTRIBUTING.md`](https://github.com/devicons/devicon/blob/develop/CONTRIBUTING.md#contributing-to-devicon) for more details. diff --git a/.github/drafts/check_devicon_object.py b/.github/drafts/check_devicon_object.py deleted file mode 100644 index b610d88e..00000000 --- a/.github/drafts/check_devicon_object.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import List - -# abandoned since it's not too hard to check devicon objects using our eyes -# however, still keep in case we need it in the future - -def check_devicon_objects(icons: List[dict]): - """ - Check that the devicon objects added is up to standard. - """ - err_msgs = [] - for icon in icons: - if type(icon["name"]) != str: - err_msgs.append("'name' must be a string, not: " + str(icon["name"])) - - try: - for tag in icon["tags"]: - if type(tag) != str: - raise TypeError() - except TypeError: - err_msgs.append("'tags' must be an array of strings, not: " + str(icon["tags"])) - break - - - if type(icon["versions"]["svg"]) != list or len(icon["versions"]["svg"]) == 0: - err_msgs.append("Icon name must be a string") - - if type(icon["versions"]["font"]) != list or len(icon["versions"]["svg"]) == 0: - err_msgs.append("Icon name must be a string") - - if type(icon["color"]) != str or "#" not in icon["color"]: - err_msgs.append("'color' must be a string in the format '#abcdef'") - - if type(icon["aliases"]) != list: - err_msgs.append("'aliases' must be an array of dicts") - - if len(err_msgs) > 0: - raise Exception("Error found in devicon.json: \n" + "\n".join(err_msgs)) diff --git a/.github/scripts/build_assets/arg_getters.py b/.github/scripts/build_assets/arg_getters.py index 51c5d557..ebe37ea7 100644 --- a/.github/scripts/build_assets/arg_getters.py +++ b/.github/scripts/build_assets/arg_getters.py @@ -67,4 +67,15 @@ def get_check_svgs_monthly_args(): parser.add_argument("icons_folder_path", help="The path to the icons folder", action=PathResolverAction) - return parser.parse_args() \ No newline at end of file + return parser.parse_args() + + +def get_release_message_args(): + """ + Get the commandline arguments for get_release_message.py. + """ + parser = ArgumentParser(description="Create a text containing the icons and features added since last release.") + parser.add_argument("token", + help="The GitHub token to access the GitHub REST API.", + type=str) + return parser.parse_args() diff --git a/.github/scripts/get_release_message.py b/.github/scripts/get_release_message.py new file mode 100644 index 00000000..af8b6b78 --- /dev/null +++ b/.github/scripts/get_release_message.py @@ -0,0 +1,75 @@ +import requests +from build_assets import arg_getters +import re + +def main(): + print("Please wait a few seconds...") + args = arg_getters.get_release_message_args() + queryPath = "https://api.github.com/repos/devicons/devicon/pulls?accept=application/vnd.github.v3+json&state=closed&per_page=100" + stopPattern = r"^(r|R)elease v" + headers = { + "Authorization": f"token {args.token}" + } + + response = requests.get(queryPath, headers=headers) + if not response: + print(f"Can't query the GitHub API. Status code is {response.status_code}. Message is {response.text}") + return + + data = response.json() + newIcons = [] + features = [] + + for pullData in data: + if re.search(stopPattern, pullData["title"]): + break + + authors = findAllAuthors(pullData, headers) + markdown = f"- [{pullData['title']}]({pullData['html_url']}) by {authors}." + + if isFeatureIcon(pullData): + newIcons.append(markdown) + else: + features.append(markdown) + + thankYou = "A huge thanks to all our maintainers and contributors for making this release possible!" + iconTitle = "**{} New Icons**\n".format(len(newIcons)) + featureTitle = "**{} New Features**\n".format(len(features)) + finalString = "{0}\n\n {1}{2}\n\n {3}{4}".format(thankYou, + iconTitle, "\n".join(newIcons), featureTitle, "\n".join(features)) + + print("--------------Here is the build message--------------\n", finalString) + + +""" + Check whether the pullData is a feature:icon PR. + :param pullData + :return true if the pullData has a label named "feature:icon" +""" +def isFeatureIcon(pullData): + for label in pullData["labels"]: + if label["name"] == "feature:icon": + return True + return False + + +""" +Find all the authors of a PR based on its commits. +:param pullData - the data of a pull request. +""" +def findAllAuthors(pullData, authHeader): + response = requests.get(pullData["commits_url"], headers=authHeader) + if not response: + print(f"Can't query the GitHub API. Status code is {response.status_code}") + print("Response is: ", response.text) + return + + commits = response.json() + authors = set() # want unique authors only + for commit in commits: + authors.add("@" + commit["author"]["login"]) + return ", ".join(list(authors)) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/icomoon_peek.py b/.github/scripts/icomoon_peek.py index b4c993cb..9e242f10 100644 --- a/.github/scripts/icomoon_peek.py +++ b/.github/scripts/icomoon_peek.py @@ -2,7 +2,6 @@ from typing import List import re import sys from selenium.common.exceptions import TimeoutException -import xml.etree.ElementTree as et # pycharm complains that build_assets is an unresolved ref # don't worry about it, the script still runs @@ -12,36 +11,28 @@ from build_assets import util def main(): - args = arg_getters.get_selenium_runner_args(True) - new_icons = filehandler.find_new_icons(args.devicon_json_path, args.icomoon_json_path) - - # get only the icon object that has the name matching the pr title - filtered_icons = find_object_added_in_this_pr(new_icons, args.pr_title) - - if len(new_icons) == 0: - sys.exit("No files need to be uploaded. Ending script...") - - if len(filtered_icons) == 0: - message = "No icons found matching the icon name in the PR's title.\n" \ - "Ensure that a new icon entry is added in the devicon.json and the PR title matches the convention here: \n" \ - "https://github.com/devicons/devicon/blob/master/CONTRIBUTING.md#overview\n" \ - "Ending script...\n" - sys.exit(message) - - # print list of new icons - print("List of new icons:", *new_icons, sep = "\n") - print("Icons being uploaded:", *filtered_icons, sep = "\n", end='\n\n') - runner = None try: + args = arg_getters.get_selenium_runner_args(True) + new_icons = filehandler.find_new_icons(args.devicon_json_path, args.icomoon_json_path) + + if len(new_icons) == 0: + raise Exception("No files need to be uploaded. Ending script...") + + # get only the icon object that has the name matching the pr title + filtered_icon = find_object_added_in_this_pr(new_icons, args.pr_title) + print("Icon being checked:", filtered_icon, sep = "\n", end='\n\n') + runner = SeleniumRunner(args.download_path, args.geckodriver_path, args.headless) - svgs = filehandler.get_svgs_paths(filtered_icons, args.icons_folder_path, True) + svgs = filehandler.get_svgs_paths([filtered_icon], args.icons_folder_path, True) screenshot_folder = filehandler.create_screenshot_folder("./") runner.upload_svgs(svgs, screenshot_folder) print("Task completed.") - except TimeoutException as e: - util.exit_with_err("Selenium Time Out Error: \n" + str(e)) + + # no errors, do this so upload-artifact won't fail + filehandler.write_to_file("./err_messages.txt", "0") except Exception as e: + filehandler.write_to_file("./err_messages.txt", str(e)) util.exit_with_err(e) finally: runner.close() @@ -52,19 +43,77 @@ def find_object_added_in_this_pr(icons: List[dict], pr_title: str): Find the icon name from the PR title. :param icons, a list of the font objects found in the devicon.json. :pr_title, the title of the PR that this workflow was called on. - :return a list containing the dictionary with the "name" + :return a dictionary with the "name" entry's value matching the name in the pr_title. - If none can be found, return an empty list. + :raise If no object can be found, raise an Exception. """ try: pattern = re.compile(r"(?<=^new icon: )\w+ (?=\(.+\))", re.I) icon_name = pattern.findall(pr_title)[0].lower().strip() # should only have one match - return [icon for icon in icons if icon["name"] == icon_name] + icon = [icon for icon in icons if icon["name"] == icon_name][0] + check_devicon_object(icon, icon_name) + return icon except IndexError: # there are no match in the findall() - return [] + raise Exception("Couldn't find an icon matching the name in the PR title.") + except ValueError as e: + raise Exception(str(e)) +def check_devicon_object(icon: dict, icon_name: str): + """ + Check that the devicon object added is up to standard. + :return a string containing the error messages if any. + """ + err_msgs = [] + try: + if icon["name"] != icon_name: + err_msgs.append("- 'name' value is not: " + icon_name) + except KeyError: + err_msgs.append("- missing key: 'name'.") + try: + for tag in icon["tags"]: + if type(tag) != str: + raise TypeError() + except TypeError: + err_msgs.append("- 'tags' must be an array of strings, not: " + str(icon["tags"])) + except KeyError: + err_msgs.append("- missing key: 'tags'.") + + try: + if type(icon["versions"]) != dict: + err_msgs.append("- 'versions' must be an object.") + except KeyError: + err_msgs.append("- missing key: 'versions'.") + + try: + if type(icon["versions"]["svg"]) != list or len(icon["versions"]["svg"]) == 0: + err_msgs.append("- must contain at least 1 svg version in a list.") + except KeyError: + err_msgs.append("- missing key: 'svg' in 'versions'.") + + try: + if type(icon["versions"]["font"]) != list or len(icon["versions"]["svg"]) == 0: + err_msgs.append("- must contain at least 1 font version in a list.") + except KeyError: + err_msgs.append("- missing key: 'font' in 'versions'.") + + try: + if type(icon["color"]) != str or "#" not in icon["color"]: + err_msgs.append("- 'color' must be a string in the format '#abcdef'") + except KeyError: + err_msgs.append("- missing key: 'color'.") + + try: + if type(icon["aliases"]) != list: + err_msgs.append("- 'aliases' must be an array.") + except KeyError: + err_msgs.append("- missing key: 'aliases'.") + + if len(err_msgs) > 0: + message = "Error found in 'devicon.json' for '{}' entry: \n{}".format(icon_name, "\n".join(err_msgs)) + raise ValueError(message) + return "" if __name__ == "__main__": main() diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 27bc3be5..00691033 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -1 +1,2 @@ -selenium==3.141.0 \ No newline at end of file +selenium==3.141.0 +requests==2.25.1 \ No newline at end of file diff --git a/.github/workflows/get_release_message.yml b/.github/workflows/get_release_message.yml new file mode 100644 index 00000000..292525e9 --- /dev/null +++ b/.github/workflows/get_release_message.yml @@ -0,0 +1,23 @@ +name: Get Release Message +on: workflow_dispatch +jobs: + build: + name: Get Fonts From Icomoon + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + + - name: Setup Python v3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r ./.github/scripts/requirements.txt + + - name: Run the get_release_message.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: python ./.github/scripts/get_release_message.py $GITHUB_TOKEN diff --git a/.github/workflows/peek_icons.yml b/.github/workflows/peek_icons.yml index 20f3455f..c85d7393 100644 --- a/.github/workflows/peek_icons.yml +++ b/.github/workflows/peek_icons.yml @@ -44,6 +44,13 @@ jobs: ./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe ./icomoon.json ./devicon.json ./icons ./ --headless --pr_title "%PR_TITLE%" + - name: Upload the err messages (created by icomoon_peek.py) + uses: actions/upload-artifact@v2 + if: always() + with: + name: err_messages + path: ./err_messages.txt + - name: Upload screenshots for comments uses: actions/upload-artifact@v2 if: success() diff --git a/.github/workflows/post_peek_screenshot.yml b/.github/workflows/post_peek_screenshot.yml index 15ae7ea7..59b55e4a 100644 --- a/.github/workflows/post_peek_screenshot.yml +++ b/.github/workflows/post_peek_screenshot.yml @@ -32,6 +32,14 @@ jobs: with: path: ./pr_num/pr_num.txt + - name: Read the err message file + if: success() + id: err_message_reader + uses: juliangruber/read-file-action@v1.0.0 + with: + path: ./err_messages/err_messages.txt + + - name: Upload screenshot of the newly made icons gotten from the artifacts id: icons_overview_img_step if: env.PEEK_STATUS == 'success' && success() @@ -87,15 +95,19 @@ jobs: MESSAGE: | Hi there, - I'm Devicons' Peek Bot and it seems we've ran into a problem (sorry!). + I'm Devicons' Peek Bot and it seems we've ran into a problem. - Please double check and fix the possible issues below: + ``` + {0} + ``` + + Make sure that: - Your svgs are named and added correctly to the /icons folder as seen [here](https://github.com/devicons/devicon/blob/master/CONTRIBUTING.md#orgGuidelines). - Your icon information has been added to the `devicon.json` as seen [here](https://github.com/devicons/devicon/blob/master/CONTRIBUTING.md#updateDevicon) - Your PR title follows the format seen [here](https://github.com/devicons/devicon/blob/master/CONTRIBUTING.md#overview) - I will retry once everything is fixed. If I still fail (sorry!) or there are other erros, the maintainers will investigate. + I will retry once everything is fixed. If I still fail or there are other error, the maintainers will investigate. Best of luck, Peek Bot :relaxed: @@ -103,4 +115,4 @@ jobs: type: create issue_number: ${{ steps.pr_num_reader.outputs.content }} token: ${{ secrets.GITHUB_TOKEN }} - body: ${{ env.MESSAGE }} + body: ${{ format(env.MESSAGE, steps.err_message_reader.outputs.content) }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f825964..7820950c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,7 @@ First of all, thanks for taking the time to contribute! This project can only gr
new icon: Icon name (versions)
.svg
file contains one version of an icon in a 0 0 128 128
viewbox. You can use a service like resize-image for scaling the svg.svg
element does not need the height
and width
attributes. However, if you do use it, ensure their values are either "128"
or "128px"
. Ex: height="128"
.svg
must use the fill
attribute instead of using classes
for colors. See here for more details.(Technology name)-(original|plain|line)(-wordmark?).
To make adding icons easier for repo maintainers, we rely on GitHub Actions, Python, Selenium, and Gulp to automate our tasks.
+We rely on GitHub Actions, Python, Selenium, Imgur, and Gulp to automate our tasks. Please feel free to take a look at the workflow files. The codes should be clear enough to follow along.
So far, the tasks in the build script are:
There are some quirks and bugs that the build scripts might run into. Listed below are the common ones and their solution
+There are some bugs that the build scripts might run into. Listed below are the common ones and their solutions
draft-release
build_icons.yml
(which has a workflow_dispatch
event trigger) and select the branch draft-release
as target branch. This will build a font version of all icons using icomoon and automatically creates a pull request to merge the build result back into draft-release
development
. Mention the release number in the pull request title (like "Build preparation for release vMAJOR.MINOR.PATCH) and add information about all new icons, fixes, features and enhancements in the description of the pull request. Take the commits as a guideline. It's also a good idea to mention and thank all contributions who participated in the release (take description of #504
as an example).development
. Mention the release number in the pull request title (like "Build preparation for release vMAJOR.MINOR.PATCH).
+ #504
as an example).
+ python ./.github/scripts/get_release_message.py $GITHUB_TOKEN
locally. Pass in your GitHub Personal Access Token for $GITHUB_TOKEN
and you should see the messages. You can also use the `workflow_dispatch` trigger in the GitHub Actions tab.
+ master
and HEAD development
. Copy the description of the earlier pull request.+ Thank you to our contributors and the IcoMoon app. Devicon would not be possible without you. +