Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE/new_icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
<!-- Tick the checkboxes to ensure you've done everything correctly -->
- [ ] PR does not match another non-stale PR currently opened
- [ ] PR name matches the format *new icon: <i>Icon name</i> (<i>versions separated by comma</i>)*. More details [here](https://github.com/devicons/devicon/wiki/Overview-on-Submitting-Icons)
- [ ] PR's base is the `develop` branch.
- [ ] PR's base is the `master` branch.
- [ ] Your icons are inside a folder as seen [here](https://github.com/devicons/devicon/wiki/Organizing-SVGs)
- [ ] SVG matches the standards laid out [here](https://github.com/devicons/devicon/wiki/SVG-Standards)
- [ ] A new object is added in the `devicon.json` file at the correct alphabetic position as seen [here](https://github.com/devicons/devicon/wiki/Updating-%60devicon.json%60)

<!-- Refer to the [contributing](https://github.com/devicons/devicon/blob/develop/CONTRIBUTING.md#contributing-to-devicon) guidelines for more details. -->
<!-- Refer to the [contributing](https://github.com/devicons/devicon/blob/master/CONTRIBUTING.md#contributing-to-devicon) guidelines for more details. -->

**This PR closes NONE**
<!-- List issues that this PR would close above. Ex: This PR closes #1, #2, #3. -->
Expand Down
78 changes: 0 additions & 78 deletions .github/scripts/build_assets/api_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import requests
import sys
import re
from typing import List
from io import FileIO


Expand Down Expand Up @@ -105,83 +104,6 @@ def find_all_authors(pull_req_data, token):
return ", ".join(["@" + author for author in list(authors)])


def label_issues(token: str, issues: List[str], labels: List[str]):
"""
Label the issues specified with the label specified.
:param token: the GitHub API token.
:param issues: the issue numbers (as str) that we are labelling.
:param labels: the labels that we are labelling.
"""
headers = {
"Authorization": f"token {token}",
"accept": "application/vnd.github.v3+json"
}
url = base_url + "issues/{}/labels"
for issue in issues:
body = {
"labels": labels
}
response = requests.post(url.format(issue), headers=headers, json=body)
if not response:
raise Exception(f"Can't label the Issue provided. Issue: {issue}, labels: {labels}, API response: " + response.text)
else:
print(f"Successfully labelled issue {issue}")


def close_issues(token: str, issues: List[str]):
"""
Close issues.
:param token: the GitHub API token.
:param issues: the issue numbers (as str) that we are labelling.
"""
headers = {
"Authorization": f"token {token}",
"accept": "application/vnd.github.v3+json"
}
url = base_url + "issues/{}"
body = {
"state": "closed"
}
for issue in issues:
response = requests.patch(url.format(issue), headers=headers, json=body)
if not response:
raise Exception(f"Can't close Issue provided. Issue: {issue}, API response: " + response.text)
else:
print(f"Successfully closed issue {issue}")


def get_issues_by_labels(token: str, labels: List[str]):
"""
Get a list of issues based on their labels.
:param token: the GitHub API token.
:param labels: the labels that we are labelling.
"""
url = base_url + "issues?per_page=100&labels={}&page={}"
headers = {
"Authorization": f"token {token}",
"accept": "application/vnd.github.v3+json"
}
issues = []
done = False
page_num = 1
while not done:
response = requests.get(url.format(",".join(labels), page_num), headers=headers)
if not response:
raise Exception(f"Can't access API. Can't get issues for labels: {labels}, API response: " + response.text)
else:
results = response.json()
if len(results) < 100:
done = True # we are done
else:
page_num += 1 # page is full => might need to check another page

# GitHub API also returns PRs for issues queries => have to check
issues_only = [issue for issue in results if issue.get("pull_request") is None]
issues.extend(issues_only)

return issues


def get_pr_by_number(token: str, pr_num: str):
url = base_url + "pulls/" + pr_num
headers = {
Expand Down
32 changes: 13 additions & 19 deletions .github/scripts/build_assets/arg_getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from build_assets.PathResolverAction import PathResolverAction


def get_selenium_runner_args(has_token=True, peek_mode=False):
def get_selenium_runner_args(has_token=True, peek_mode=False, build_since=False):
"""
Get the commandline arguments for the icomoon_peek.py and
icomoon_build.py.
Expand Down Expand Up @@ -34,11 +34,16 @@ def get_selenium_runner_args(has_token=True, peek_mode=False):
action=PathResolverAction)

if peek_mode:
parser.add_argument("pr_title",
help="The title of the PR that we are peeking at")
parser.add_argument("--base",
help="Commit to diff against to find the PR's changes (HEAD^1 in a PR merge commit)",
default="HEAD^1")
if has_token != False:
parser.add_argument("token",
help="The GitHub token to access the GitHub REST API.")
if build_since:
parser.add_argument("--since",
help="Commit to diff against to find updated SVGs (usually the last build commit)",
default=None)

return parser.parse_args()

Expand All @@ -50,7 +55,7 @@ def get_check_icon_pr_args():
parser = ArgumentParser(description="Check the SVGs to ensure their attributes are correct. Run whenever a PR is opened")

parser.add_argument("pr_title",
help="The title of the PR that we are peeking at")
help="The title of the PR that we are checking")

parser.add_argument("icons_folder_path",
help="The path to the icons folder",
Expand All @@ -60,6 +65,10 @@ def get_check_icon_pr_args():
help="The path to the devicon.json",
action=PathResolverAction)

parser.add_argument("--base",
help="Commit to diff against to find the PR's changes (HEAD^1 in a PR merge commit)",
default="HEAD^1")

return parser.parse_args()


Expand All @@ -71,18 +80,3 @@ def get_release_message_args():
parser.add_argument("token",
help="The GitHub token to access the GitHub REST API.")
return parser.parse_args()


def get_in_develop_labeler_args():
"""
Get the commandline arguments for in_develop_labeler.py.
"""
parser = ArgumentParser(description="Parse the PR body to find the issue(s) we are labelling.")
parser.add_argument("token",
help="The GitHub token to access the GitHub REST API.",
type=str)

parser.add_argument("pr_num",
help="The PR's number",
type=str)
return parser.parse_args()
61 changes: 30 additions & 31 deletions .github/scripts/build_assets/selenium_runner/BuildSeleniumRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ class BuildSeleniumRunner(SeleniumRunner):
def build_icons(self, icomoon_json_path: str,
zip_path: Path, svgs: List[str], screenshot_folder: str):
self.upload_icomoon(icomoon_json_path)
# necessary so we can take screenshot of only the
# recently uploaded icons later
# Deselect all existing icons BEFORE uploading new SVGs. upload_svgs()
# then selects only the new icons for the "new icons only" screenshot.
# download_icomoon_fonts() calls select_all before opening the Generate
# Font page, so the full icon set is always used for font generation.
#
# Never open the Generate Font page with only some icons selected:
# icomoon.io assigns codepoints to the selection when the page opens, so
# a partial selection would give the new icons the first codepoints and
# shift existing glyphs. icomoon_build_githubless.py verifies this.
self.deselect_all_icons_in_top_set()
self.upload_svgs(svgs, screenshot_folder)
self.take_icon_screenshot(screenshot_folder)
self.download_icomoon_fonts(zip_path)
self.download_icomoon_fonts(zip_path, screenshot_folder)

def upload_icomoon(self, icomoon_json_path: str):
"""
Expand Down Expand Up @@ -90,36 +96,17 @@ def upload_svgs(self, svgs: List[str], screenshot_folder: str):
message = "BuildSeleniumRunner - Issues found when uploading SVGs:\n"
raise Exception(message + '\n'.join(err_messages))

# take a screenshot of the svgs that were just added
# select the latest icons
# Select only the newly uploaded SVGs, so the screenshot below shows just
# those. icomoon.io doesn't select uploaded icons by itself; new icons
# are the first ones in the top set.
self.switch_toolbar_option(IcomoonOptionState.SELECT)
self.select_all_icons_in_top_set()
self.click_latest_icons_in_top_set(len(svgs))
print(f"Selected the {len(svgs)} new icon(s).", file=self.log_output)
new_svgs_path = str(Path(screenshot_folder, "new_svgs.png").resolve())
self.driver.save_screenshot(new_svgs_path)

print("Finished uploading the svgs...", file=self.log_output)

def take_icon_screenshot(self, screenshot_folder: str):
"""
Take the overview icon screenshot of the uploaded icons.
:param svgs: a list of svg Paths that we'll upload to icomoon.
:param screenshot_folder: the name of the screenshot_folder.
"""
# take pictures
print("Taking screenshot of the new icons...", file=self.log_output)
self.go_to_generate_font_page()

# take an overall screenshot of the icons that were just added
# also include the glyph count
new_icons_path = str(Path(screenshot_folder, "new_icons.png").resolve())
main_content_xpath = "/html/body/div[4]/div[2]/div/div[1]"
main_content = self.driver.find_element_by_xpath(main_content_xpath)

# wait a bit for all the icons to load before we take a pic
time.sleep(SeleniumRunner.MED_WAIT_IN_SEC)
main_content.screenshot(new_icons_path)
print("Saved screenshot of the new icons...", file=self.log_output)

def go_to_generate_font_page(self):
"""
Go to the generate font page. Also handles the "Deselect Icons
Expand All @@ -135,19 +122,31 @@ def go_to_generate_font_page(self):
else:
raise Exception(f"Unexpected alert found: {alert}")

def download_icomoon_fonts(self, zip_path: Path):
def download_icomoon_fonts(self, zip_path: Path, screenshot_folder: str):
"""
Download the icomoon.zip from icomoon.io. Also take a picture of
what the icons look like.
Download the icomoon.zip from icomoon.io. Also take a screenshot of
the Generate Font page, which shows the total glyph count.
:param zip_path: the path to the zip file after it's downloaded.
:param screenshot_folder: the name of the screenshot_folder.
"""
print("Downloading Font files...", file=self.log_output)
if self.current_page != IcomoonPage.SELECTION:
self.go_to_page(IcomoonPage.SELECTION)

# select_all is required here — after deselect_all + new SVG uploads,
# only the new icons are selected. We must select everything before
# opening the Generate Font page, or the existing icons would be missing
# from the output and get new codepoints (see build_icons).
self.select_all_icons_in_top_set()
self.go_to_generate_font_page()

# wait a bit for the glyphs to render; full-page screenshot to avoid
# canvas taint restrictions
time.sleep(SeleniumRunner.MED_WAIT_IN_SEC)
font_glyphs_path = str(Path(screenshot_folder, "font_glyphs.png").resolve())
self.driver.save_screenshot(font_glyphs_path)
print("Saved screenshot of the Generate Font page.", file=self.log_output)

download_btn = WebDriverWait(self.driver, SeleniumRunner.LONG_WAIT_IN_SEC).until(
ec.presence_of_element_located((By.CSS_SELECTOR, "button.btn4 span"))
)
Expand Down
52 changes: 16 additions & 36 deletions .github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ def peek(self, svgs: List[str], screenshot_folder: str, icon_info: dict):
Upload the SVGs and peek at how Icomoon interpret its SVGs and
font versions.
:param svgs: a list of svg Paths that we'll upload to icomoon.
:param screenshot_folder: the name of the screenshot_folder.
:param screenshot_folder: the name of the screenshot_folder.
:param icon_info: a dictionary containing info on an icon. Taken from the devicon.json.
:return an array of svgs with strokes as strings. These show which icon
contains stroke.
"""
messages = self.peek_svgs(svgs, screenshot_folder)
self.peek_icons(screenshot_folder, icon_info)
messages = self.peek_svgs(svgs, screenshot_folder)
self.peek_icons(screenshot_folder, icon_info, len(svgs))
return messages

def peek_svgs(self, svgs: List[str], screenshot_folder: str):
Expand Down Expand Up @@ -52,24 +52,19 @@ def peek_svgs(self, svgs: List[str], screenshot_folder: str):

self.edit_svg(screenshot_folder, i)

# take a screenshot of the svgs that were just added
self.select_all_icons_in_top_set()
new_svgs_path = str(Path(screenshot_folder, "new_svgs.png").resolve())
icon_set_xpath = "/html/body/div[4]/div[1]/div[2]/div[1]"
icon_set = self.driver.find_element_by_xpath(icon_set_xpath)
icon_set.screenshot(new_svgs_path)

print("Finished peeking the svgs...", file=self.log_output)
return svgs_with_strokes

def peek_icons(self, screenshot_folder: str, icon_info: dict):
def peek_icons(self, screenshot_folder: str, icon_info: dict, num_svgs: int):
"""
Peek at the icon versions of the SVGs that were uploaded.
:param screenshot_folder: the name of the screenshot_folder.
:param screenshot_folder: the name of the screenshot_folder.
:param icon_info: a dictionary containing info on an icon. Taken from the devicon.json.
:param num_svgs: the number of SVGs that were uploaded (used to guard against
extra icons from icomoon's default sets leaking into the font preview).
"""
print("Begin peeking at the icons...", file=self.log_output)
# ensure all icons in the set is selected.
# Select only the newly uploaded icons in the top set before generating font.
self.select_all_icons_in_top_set()
self.go_to_page(IcomoonPage.GENERATE_FONT)
alert = self.test_for_possible_alert(self.MED_WAIT_IN_SEC)
Expand All @@ -80,43 +75,28 @@ def peek_icons(self, screenshot_folder: str, icon_info: dict):
else:
raise Exception(f"Unexpected alert found: {alert}")

# take an overall screenshot of the icons that were just added
# also include the glyph count
new_icons_path = str(Path(screenshot_folder, "new_icons.png").resolve())
main_content_xpath = "/html/body/div[4]/div[2]/div/div[1]"
main_content = self.driver.find_element_by_xpath(main_content_xpath)
main_content.screenshot(new_icons_path);

# go in reverse order so we get the oldest icon first
# Collect glyph divs; reverse so screenshots are ordered oldest-first
# (matching the SVG upload order). Cap at num_svgs as a safety guard
# in case any icons from icomoon's default sets bleed into glyphSet0.
icon_divs_xpath = f'//div[@id="glyphSet0"]/div'
icon_divs = self.driver.find_elements_by_xpath(icon_divs_xpath)
icon_divs.reverse()
i = 0
for icon_div in icon_divs:
if not icon_div.is_displayed():
continue
all_icon_divs = self.driver.find_elements_by_xpath(icon_divs_xpath)
all_icon_divs.reverse()
icon_divs = [d for d in all_icon_divs if d.is_displayed()][:num_svgs]

for i, icon_div in enumerate(icon_divs):
icon_screenshot = str(
Path(screenshot_folder, f"new_icon_{i}.png").resolve()
)
icon_div.screenshot(icon_screenshot)

i += 1

# test the colors
style = "#glyphSet0 span:first-of-type {color: " + icon_info["color"] + "}"
script = f"document.styleSheets[0].insertRule('{style}', 0)"
self.driver.execute_script(script)
i = 0
for icon_div in icon_divs:
if not icon_div.is_displayed():
continue

for i, icon_div in enumerate(icon_divs):
icon_screenshot = str(
Path(screenshot_folder, f"new_colored_icon_{i}.png").resolve()
)
icon_div.screenshot(icon_screenshot)

i += 1

print("Finished peeking the icons...", file=self.log_output)
Loading
Loading