mirror of
https://github.com/konpa/devicon.git
synced 2025-08-05 06:07:28 +02:00
Feature: Peek and Build-Bot Upgrade (#806)
* Refactored peek script into a class * Post-peek workflow now upload the new screenshots * Refactored BuildSeleniumRunner into a class * Updated build_icons.yml to reflect new changes * Fixed issue with building icons that were already in the app * Build script will take screenshot of new icons * Update post peek yaml message * Added alerts * Peek script now check for strokes in icons * Updated post_peek's strokes in svgs message * Updated post_peek script's message * Updated post_peek's message * Refactored get_release_message into icomoon_build * Change devicon.css name to devicon-base.css * Updated post_peek message * Added update icon as a valid PR title for bot-peek * Add \n char to SVG after it gets optimized * Fixed error with 'update icon' regex * Build script now batch issues when upload SVG * Addressed build-bot's screenshot order * Apply suggestions from code review Co-authored-by: David Leal <halfpacho@gmail.com> Co-authored-by: David Leal <halfpacho@gmail.com>
This commit is contained in:
267
.github/scripts/build_assets/SeleniumRunner.py
vendored
267
.github/scripts/build_assets/SeleniumRunner.py
vendored
@@ -1,267 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
from pathlib import Path
|
|
||||||
import time
|
|
||||||
|
|
||||||
from selenium.webdriver.firefox.webdriver import WebDriver
|
|
||||||
from selenium.webdriver.firefox.options import Options
|
|
||||||
from selenium.webdriver.common.by import By
|
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
|
||||||
from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException
|
|
||||||
|
|
||||||
|
|
||||||
class SeleniumRunner:
|
|
||||||
"""
|
|
||||||
A runner that upload and download Icomoon resources using Selenium.
|
|
||||||
The WebDriver will use Firefox.
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
The long wait time for the driver in seconds.
|
|
||||||
"""
|
|
||||||
LONG_WAIT_IN_SEC = 25
|
|
||||||
|
|
||||||
"""
|
|
||||||
The medium wait time for the driver in seconds.
|
|
||||||
"""
|
|
||||||
MED_WAIT_IN_SEC = 6
|
|
||||||
|
|
||||||
"""
|
|
||||||
The short wait time for the driver in seconds.
|
|
||||||
"""
|
|
||||||
SHORT_WAIT_IN_SEC = 0.6
|
|
||||||
|
|
||||||
"""
|
|
||||||
The Icomoon Url.
|
|
||||||
"""
|
|
||||||
ICOMOON_URL = "https://icomoon.io/app/#/select"
|
|
||||||
|
|
||||||
def __init__(self, download_path: str,
|
|
||||||
geckodriver_path: str, headless: bool):
|
|
||||||
"""
|
|
||||||
Create a SeleniumRunner object.
|
|
||||||
:param download_path: the location where you want to download
|
|
||||||
the icomoon.zip to.
|
|
||||||
:param geckodriver_path: the path to the firefox executable.
|
|
||||||
:param headless: whether to run browser in headless (no UI) mode.
|
|
||||||
"""
|
|
||||||
self.driver = None
|
|
||||||
self.set_options(download_path, geckodriver_path, headless)
|
|
||||||
|
|
||||||
def set_options(self, download_path: str, geckodriver_path: str,
|
|
||||||
headless: bool):
|
|
||||||
"""
|
|
||||||
Build the WebDriver with Firefox Options allowing downloads and
|
|
||||||
set download to download_path.
|
|
||||||
:param download_path: the location where you want to download
|
|
||||||
:param geckodriver_path: the path to the firefox executable.
|
|
||||||
the icomoon.zip to.
|
|
||||||
:param headless: whether to run browser in headless (no UI) mode.
|
|
||||||
|
|
||||||
:raises AssertionError: if the page title does not contain
|
|
||||||
"IcoMoon App".
|
|
||||||
"""
|
|
||||||
options = Options()
|
|
||||||
allowed_mime_types = "application/zip, application/gzip, application/octet-stream"
|
|
||||||
# disable prompt to download from Firefox
|
|
||||||
options.set_preference("browser.helperApps.neverAsk.saveToDisk", allowed_mime_types)
|
|
||||||
options.set_preference("browser.helperApps.neverAsk.openFile", allowed_mime_types)
|
|
||||||
|
|
||||||
# set the default download path to downloadPath
|
|
||||||
options.set_preference("browser.download.folderList", 2)
|
|
||||||
options.set_preference("browser.download.dir", download_path)
|
|
||||||
options.headless = headless
|
|
||||||
|
|
||||||
self.driver = WebDriver(options=options, executable_path=geckodriver_path)
|
|
||||||
self.driver.get(self.ICOMOON_URL)
|
|
||||||
assert "IcoMoon App" in self.driver.title
|
|
||||||
# wait until the whole web page is loaded by testing the hamburger input
|
|
||||||
WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
|
||||||
ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]"))
|
|
||||||
)
|
|
||||||
print("Accessed icomoon.io")
|
|
||||||
|
|
||||||
|
|
||||||
def upload_icomoon(self, icomoon_json_path: str):
|
|
||||||
"""
|
|
||||||
Upload the icomoon.json to icomoon.io.
|
|
||||||
:param icomoon_json_path: a path to the iconmoon.json.
|
|
||||||
:raises TimeoutException: happens when elements are not found.
|
|
||||||
"""
|
|
||||||
print("Uploading icomoon.json file...")
|
|
||||||
self.click_hamburger_input()
|
|
||||||
|
|
||||||
# find the file input and enter the file path
|
|
||||||
import_btn = self.driver.find_element(By.XPATH, "(//li[@class='file'])[1]//input")
|
|
||||||
import_btn.send_keys(icomoon_json_path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
confirm_btn = WebDriverWait(self.driver, SeleniumRunner.MED_WAIT_IN_SEC).until(
|
|
||||||
ec.element_to_be_clickable((By.XPATH, "//div[@class='overlay']//button[text()='Yes']"))
|
|
||||||
)
|
|
||||||
confirm_btn.click()
|
|
||||||
except SeleniumTimeoutException as e:
|
|
||||||
raise Exception("Cannot find the confirm button when uploading the icomoon.json" \
|
|
||||||
"Ensure that the icomoon.json is in the correct format for Icomoon.io")
|
|
||||||
|
|
||||||
print("JSON file uploaded.")
|
|
||||||
|
|
||||||
def upload_svgs(self, svgs: List[str], screenshot_folder: str=""):
|
|
||||||
"""
|
|
||||||
Upload the SVGs provided in folder_info
|
|
||||||
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
|
||||||
:param screenshot_folder: the name of the screenshot_folder. If
|
|
||||||
the value is provided, it means the user want to take a screenshot
|
|
||||||
of each icon.
|
|
||||||
"""
|
|
||||||
print("Uploading SVGs...")
|
|
||||||
|
|
||||||
edit_mode_btn = self.driver.find_element_by_css_selector(
|
|
||||||
"div.btnBar button i.icon-edit"
|
|
||||||
)
|
|
||||||
edit_mode_btn.click()
|
|
||||||
|
|
||||||
self.click_hamburger_input()
|
|
||||||
|
|
||||||
for i in range(len(svgs)):
|
|
||||||
import_btn = self.driver.find_element_by_css_selector(
|
|
||||||
"li.file input[type=file]"
|
|
||||||
)
|
|
||||||
import_btn.send_keys(svgs[i])
|
|
||||||
print(f"Uploaded {svgs[i]}")
|
|
||||||
self.test_for_possible_alert(self.SHORT_WAIT_IN_SEC, "Dismiss")
|
|
||||||
self.click_on_just_added_icon(screenshot_folder, i)
|
|
||||||
|
|
||||||
# take a screenshot of the icons that were just added
|
|
||||||
new_icons_path = str(Path(screenshot_folder, "new_icons.png").resolve())
|
|
||||||
self.driver.save_screenshot(new_icons_path);
|
|
||||||
|
|
||||||
print("Finished uploading the svgs...")
|
|
||||||
|
|
||||||
def click_hamburger_input(self):
|
|
||||||
"""
|
|
||||||
Click the hamburger input until the pop up menu appears. This
|
|
||||||
method is needed because sometimes, we need to click the hamburger
|
|
||||||
input two times before the menu appears.
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
hamburger_input = self.driver.find_element_by_xpath(
|
|
||||||
"(//i[@class='icon-menu'])[2]"
|
|
||||||
)
|
|
||||||
|
|
||||||
menu_appear_callback = ec.element_to_be_clickable(
|
|
||||||
(By.CSS_SELECTOR, "h1 ul.menuList2")
|
|
||||||
)
|
|
||||||
|
|
||||||
while not menu_appear_callback(self.driver):
|
|
||||||
hamburger_input.click()
|
|
||||||
|
|
||||||
def test_for_possible_alert(self, wait_period: float, btn_text: str):
|
|
||||||
"""
|
|
||||||
Test for the possible alert when we upload the svgs.
|
|
||||||
:param wait_period: the wait period for the possible alert
|
|
||||||
in seconds.
|
|
||||||
:param btn_text: the text that the alert's button will have.
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
dismiss_btn = WebDriverWait(self.driver, wait_period, 0.15).until(
|
|
||||||
ec.element_to_be_clickable(
|
|
||||||
(By.XPATH, f"//div[@class='overlay']//button[text()='{btn_text}']"))
|
|
||||||
)
|
|
||||||
dismiss_btn.click()
|
|
||||||
except SeleniumTimeoutException:
|
|
||||||
pass # nothing found => everything is good
|
|
||||||
|
|
||||||
def click_on_just_added_icon(self, screenshot_folder: str, index: int):
|
|
||||||
"""
|
|
||||||
Click on the most recently added icon so we can remove the colors
|
|
||||||
and take a snapshot if needed.
|
|
||||||
"""
|
|
||||||
recently_uploaded_icon = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
|
||||||
ec.element_to_be_clickable((By.XPATH, "//div[@id='set0']//mi-box[1]//div"))
|
|
||||||
)
|
|
||||||
recently_uploaded_icon.click()
|
|
||||||
|
|
||||||
self.remove_color_from_icon()
|
|
||||||
|
|
||||||
if screenshot_folder:
|
|
||||||
screenshot_path = str(Path(screenshot_folder, f"screenshot_{index}.png").resolve())
|
|
||||||
self.driver.save_screenshot(screenshot_path)
|
|
||||||
print("Took screenshot and saved it at " + screenshot_path)
|
|
||||||
|
|
||||||
close_btn = self.driver \
|
|
||||||
.find_element_by_css_selector("div.overlayWindow i.icon-close")
|
|
||||||
close_btn.click()
|
|
||||||
|
|
||||||
def remove_color_from_icon(self):
|
|
||||||
"""
|
|
||||||
Remove the color from the most recent uploaded icon.
|
|
||||||
This is because some SVG have colors in them and we don't want to
|
|
||||||
force contributors to remove them in case people want the colored SVGs.
|
|
||||||
The color removal is also necessary so that the Icomoon-generated
|
|
||||||
icons fit within one font symbol/ligiature.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
color_tab = WebDriverWait(self.driver, self.SHORT_WAIT_IN_SEC).until(
|
|
||||||
ec.element_to_be_clickable((By.CSS_SELECTOR, "div.overlayWindow i.icon-droplet"))
|
|
||||||
)
|
|
||||||
color_tab.click()
|
|
||||||
|
|
||||||
remove_color_btn = self.driver \
|
|
||||||
.find_element_by_css_selector("div.overlayWindow i.icon-droplet-cross")
|
|
||||||
remove_color_btn.click()
|
|
||||||
except SeleniumTimeoutException:
|
|
||||||
pass # do nothing cause sometimes, the color tab doesn't appear in the site
|
|
||||||
|
|
||||||
def download_icomoon_fonts(self, zip_path: Path):
|
|
||||||
"""
|
|
||||||
Download the icomoon.zip from icomoon.io.
|
|
||||||
:param zip_path: the path to the zip file after it's downloaded.
|
|
||||||
"""
|
|
||||||
# select all the svgs so that the newly added svg are part of the collection
|
|
||||||
self.click_hamburger_input()
|
|
||||||
select_all_button = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
|
||||||
ec.element_to_be_clickable((By.XPATH, "//button[text()='Select All']"))
|
|
||||||
)
|
|
||||||
select_all_button.click()
|
|
||||||
|
|
||||||
print("Downloading Font files...")
|
|
||||||
font_tab = self.driver.find_element_by_css_selector(
|
|
||||||
"a[href='#/select/font']"
|
|
||||||
)
|
|
||||||
font_tab.click()
|
|
||||||
|
|
||||||
self.test_for_possible_alert(self.MED_WAIT_IN_SEC, "Continue")
|
|
||||||
download_btn = WebDriverWait(self.driver, SeleniumRunner.LONG_WAIT_IN_SEC).until(
|
|
||||||
ec.presence_of_element_located((By.CSS_SELECTOR, "button.btn4 span"))
|
|
||||||
)
|
|
||||||
download_btn.click()
|
|
||||||
if self.wait_for_zip(zip_path):
|
|
||||||
print("Font files downloaded.")
|
|
||||||
else:
|
|
||||||
raise TimeoutError(f"Couldn't find {zip_path} after download button was clicked.")
|
|
||||||
|
|
||||||
def wait_for_zip(self, zip_path: Path) -> bool:
|
|
||||||
"""
|
|
||||||
Wait for the zip file to be downloaded by checking for its existence
|
|
||||||
in the download path. Wait time is self.LONG_WAIT_IN_SEC and check time
|
|
||||||
is 1 sec.
|
|
||||||
:param zip_path: the path to the zip file after it's
|
|
||||||
downloaded.
|
|
||||||
:return: True if the file is found within the allotted time, else
|
|
||||||
False.
|
|
||||||
"""
|
|
||||||
end_time = time.time() + self.LONG_WAIT_IN_SEC
|
|
||||||
while time.time() <= end_time:
|
|
||||||
if zip_path.exists():
|
|
||||||
return True
|
|
||||||
time.sleep(1)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Close the SeleniumRunner instance.
|
|
||||||
"""
|
|
||||||
print("Closing down SeleniumRunner...")
|
|
||||||
self.driver.quit()
|
|
57
.github/scripts/build_assets/api_handler.py
vendored
57
.github/scripts/build_assets/api_handler.py
vendored
@@ -2,10 +2,38 @@ import requests
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def get_merged_pull_reqs_since_last_release(token):
|
||||||
|
"""
|
||||||
|
Get all the merged pull requests since the last release.
|
||||||
|
"""
|
||||||
|
stopPattern = r"^(r|R)elease v"
|
||||||
|
pull_reqs = []
|
||||||
|
found_last_release = False
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
print("Getting PRs since last release.")
|
||||||
|
while not found_last_release:
|
||||||
|
data = get_merged_pull_reqs(token, page)
|
||||||
|
# assume we don't encounter it during the loop
|
||||||
|
last_release_index = 101
|
||||||
|
|
||||||
|
for i in range(len(data)):
|
||||||
|
if re.search(stopPattern, data[i]["title"]):
|
||||||
|
found_last_release = True
|
||||||
|
last_release_index = i
|
||||||
|
break
|
||||||
|
pull_reqs.extend(data[:last_release_index])
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
# should contain all the PRs since last release
|
||||||
|
return pull_reqs
|
||||||
|
|
||||||
|
|
||||||
def get_merged_pull_reqs(token, page):
|
def get_merged_pull_reqs(token, page):
|
||||||
"""
|
"""
|
||||||
Get the merged pull requests based on page. There are
|
Get the merged pull requests based on page. There are
|
||||||
100 results page. See https://docs.github.com/en/rest/reference/pulls
|
100 results per page. See https://docs.github.com/en/rest/reference/pulls
|
||||||
for more details on the parameters.
|
for more details on the parameters.
|
||||||
:param token, a GitHub API token.
|
:param token, a GitHub API token.
|
||||||
:param page, the page number.
|
:param page, the page number.
|
||||||
@@ -71,30 +99,3 @@ def find_all_authors(pull_req_data, token):
|
|||||||
authors.add(commit["commit"]["author"]["name"])
|
authors.add(commit["commit"]["author"]["name"])
|
||||||
print(f"This URL didn't have an `author` attribute: {pull_req_data['commits_url']}")
|
print(f"This URL didn't have an `author` attribute: {pull_req_data['commits_url']}")
|
||||||
return ", ".join(["@" + author for author in list(authors)])
|
return ", ".join(["@" + author for author in list(authors)])
|
||||||
|
|
||||||
|
|
||||||
def get_merged_pull_reqs_since_last_release(token):
|
|
||||||
"""
|
|
||||||
Get all the merged pull requests since the last release.
|
|
||||||
"""
|
|
||||||
stopPattern = r"^(r|R)elease v"
|
|
||||||
pull_reqs = []
|
|
||||||
found_last_release = False
|
|
||||||
page = 1
|
|
||||||
|
|
||||||
print("Getting PRs since last release.")
|
|
||||||
while not found_last_release:
|
|
||||||
data = get_merged_pull_reqs(token, page)
|
|
||||||
# assume we don't encounter it during the loop
|
|
||||||
last_release_index = 101
|
|
||||||
|
|
||||||
for i in range(len(data)):
|
|
||||||
if re.search(stopPattern, data[i]["title"]):
|
|
||||||
found_last_release = True
|
|
||||||
last_release_index = i
|
|
||||||
break
|
|
||||||
pull_reqs.extend(data[:last_release_index])
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
# should contain all the PRs since last release
|
|
||||||
return pull_reqs
|
|
||||||
|
6
.github/scripts/build_assets/filehandler.py
vendored
6
.github/scripts/build_assets/filehandler.py
vendored
@@ -91,7 +91,7 @@ def get_icon_svgs_paths(folder_path: Path, icon_info: dict,
|
|||||||
for font_version in icon_info["versions"]["font"]:
|
for font_version in icon_info["versions"]["font"]:
|
||||||
# if it's an alias, we don't want to make it into an icon
|
# if it's an alias, we don't want to make it into an icon
|
||||||
if is_alias(font_version, aliases):
|
if is_alias(font_version, aliases):
|
||||||
print(f"Skipping this font since it's an alias: {icon_info['name']}-{font_version}.svg")
|
print(f"Finding SVG filepaths: skipping this font since it's an alias: {icon_info['name']}-{font_version}.svg")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
file_name = f"{icon_info['name']}-{font_version}.svg"
|
file_name = f"{icon_info['name']}-{font_version}.svg"
|
||||||
@@ -177,7 +177,7 @@ def rename_extracted_files(extract_path: str):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"old": Path(extract_path, "style.css"),
|
"old": Path(extract_path, "style.css"),
|
||||||
"new": Path(extract_path, "devicon.css")
|
"new": Path(extract_path, "devicon-base.css")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ def create_screenshot_folder(dir, screenshot_name: str="screenshots/"):
|
|||||||
try:
|
try:
|
||||||
os.mkdir(screenshot_folder)
|
os.mkdir(screenshot_folder)
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
print(f"{screenshot_folder} already exist. Script will do nothing.")
|
print(f"{screenshot_folder} already exist. Not creating new folder.")
|
||||||
finally:
|
finally:
|
||||||
return str(screenshot_folder)
|
return str(screenshot_folder)
|
||||||
|
|
||||||
|
171
.github/scripts/build_assets/selenium_runner/BuildSeleniumRunner.py
vendored
Normal file
171
.github/scripts/build_assets/selenium_runner/BuildSeleniumRunner.py
vendored
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
from typing import List
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException
|
||||||
|
|
||||||
|
from build_assets.selenium_runner.SeleniumRunner import SeleniumRunner
|
||||||
|
from build_assets.selenium_runner.enums import IcomoonPage, IcomoonAlerts, IcomoonOptionState
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
|
||||||
|
def upload_icomoon(self, icomoon_json_path: str):
|
||||||
|
"""
|
||||||
|
Upload the icomoon.json to icomoon.io.
|
||||||
|
:param icomoon_json_path: a path to the iconmoon.json.
|
||||||
|
:raises TimeoutException: happens when elements are not found.
|
||||||
|
"""
|
||||||
|
print("Uploading icomoon.json file...")
|
||||||
|
|
||||||
|
# find the file input and enter the file path
|
||||||
|
import_btn = self.driver.find_element_by_css_selector(
|
||||||
|
SeleniumRunner.GENERAL_IMPORT_BUTTON_CSS
|
||||||
|
)
|
||||||
|
import_btn.send_keys(icomoon_json_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
confirm_btn = WebDriverWait(self.driver, SeleniumRunner.MED_WAIT_IN_SEC).until(
|
||||||
|
ec.element_to_be_clickable((By.XPATH, "//div[@class='overlay']//button[text()='Yes']"))
|
||||||
|
)
|
||||||
|
confirm_btn.click()
|
||||||
|
except SeleniumTimeoutException as e:
|
||||||
|
raise Exception("Cannot find the confirm button when uploading the icomoon.json" \
|
||||||
|
"Ensure that the icomoon.json is in the correct format for Icomoon.io")
|
||||||
|
|
||||||
|
print("JSON file uploaded.")
|
||||||
|
|
||||||
|
def upload_svgs(self, svgs: List[str], screenshot_folder: str):
|
||||||
|
"""
|
||||||
|
Upload the SVGs provided in svgs. This will upload the
|
||||||
|
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
||||||
|
:param screenshot_folder: the name of the screenshot_folder.
|
||||||
|
"""
|
||||||
|
print("Uploading SVGs...")
|
||||||
|
|
||||||
|
import_btn = self.driver.find_element_by_css_selector(
|
||||||
|
SeleniumRunner.SET_IMPORT_BUTTON_CSS
|
||||||
|
)
|
||||||
|
|
||||||
|
# there could be at most 2 alerts when we upload an SVG.
|
||||||
|
possible_alerts_amount = 2
|
||||||
|
err_messages = []
|
||||||
|
for i in range(len(svgs)):
|
||||||
|
import_btn.send_keys(svgs[i])
|
||||||
|
print(f"Uploading {svgs[i]}")
|
||||||
|
|
||||||
|
# see if there are stroke messages or replacing icon message
|
||||||
|
# there should be none of the second kind
|
||||||
|
for j in range(possible_alerts_amount):
|
||||||
|
alert = self.test_for_possible_alert(self.SHORT_WAIT_IN_SEC)
|
||||||
|
if alert == None:
|
||||||
|
pass # all good
|
||||||
|
elif alert == IcomoonAlerts.STROKES_GET_IGNORED_WARNING:
|
||||||
|
message = f"SVG contained strokes: {svgs[i]}."
|
||||||
|
err_messages.append(message)
|
||||||
|
self.click_alert_button(self.ALERTS[alert]["buttons"]["DISMISS"])
|
||||||
|
elif alert == IcomoonAlerts.REPLACE_OR_REIMPORT_ICON:
|
||||||
|
message = f"Duplicated SVG: {svgs[i]}."
|
||||||
|
err_messages.append(message)
|
||||||
|
self.click_alert_button(self.ALERTS[alert]["buttons"]["REIMPORT"])
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unexpected alert found: {alert}")
|
||||||
|
|
||||||
|
self.edit_svg()
|
||||||
|
print(f"Finished editing icon.")
|
||||||
|
|
||||||
|
if err_messages != []:
|
||||||
|
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
|
||||||
|
self.switch_toolbar_option(IcomoonOptionState.SELECT)
|
||||||
|
self.click_latest_icons_in_top_set(len(svgs))
|
||||||
|
new_svgs_path = str(Path(screenshot_folder, "new_svgs.png").resolve())
|
||||||
|
self.driver.save_screenshot(new_svgs_path);
|
||||||
|
|
||||||
|
print("Finished uploading the svgs...")
|
||||||
|
|
||||||
|
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...")
|
||||||
|
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)
|
||||||
|
main_content.screenshot(new_icons_path)
|
||||||
|
print("Saved screenshot of the new icons...")
|
||||||
|
|
||||||
|
def go_to_generate_font_page(self):
|
||||||
|
"""
|
||||||
|
Go to the generate font page. Also handles the "Deselect Icons
|
||||||
|
with Strokes" alert.
|
||||||
|
"""
|
||||||
|
self.go_to_page(IcomoonPage.GENERATE_FONT)
|
||||||
|
alert = self.test_for_possible_alert(self.MED_WAIT_IN_SEC)
|
||||||
|
if alert == None:
|
||||||
|
pass # all good
|
||||||
|
elif alert == IcomoonAlerts.DESELECT_ICONS_CONTAINING_STROKES:
|
||||||
|
message = f"One of SVGs contained strokes. This should not happen."
|
||||||
|
raise Exception(message)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unexpected alert found: {alert}")
|
||||||
|
|
||||||
|
def download_icomoon_fonts(self, zip_path: Path):
|
||||||
|
"""
|
||||||
|
Download the icomoon.zip from icomoon.io. Also take a picture of
|
||||||
|
what the icons look like.
|
||||||
|
:param zip_path: the path to the zip file after it's downloaded.
|
||||||
|
"""
|
||||||
|
print("Downloading Font files...")
|
||||||
|
if self.current_page != IcomoonPage.SELECTION:
|
||||||
|
self.go_to_page(IcomoonPage.SELECTION)
|
||||||
|
|
||||||
|
self.select_all_icons_in_top_set()
|
||||||
|
self.go_to_generate_font_page()
|
||||||
|
|
||||||
|
download_btn = WebDriverWait(self.driver, SeleniumRunner.LONG_WAIT_IN_SEC).until(
|
||||||
|
ec.presence_of_element_located((By.CSS_SELECTOR, "button.btn4 span"))
|
||||||
|
)
|
||||||
|
download_btn.click()
|
||||||
|
if self.wait_for_zip(zip_path):
|
||||||
|
print("Font files downloaded.")
|
||||||
|
else:
|
||||||
|
raise TimeoutError(f"Couldn't find {zip_path} after download button was clicked.")
|
||||||
|
|
||||||
|
def wait_for_zip(self, zip_path: Path) -> bool:
|
||||||
|
"""
|
||||||
|
Wait for the zip file to be downloaded by checking for its existence
|
||||||
|
in the download path. Wait time is self.LONG_WAIT_IN_SEC and check time
|
||||||
|
is 1 sec.
|
||||||
|
:param zip_path: the path to the zip file after it's
|
||||||
|
downloaded.
|
||||||
|
:return: True if the file is found within the allotted time, else
|
||||||
|
False.
|
||||||
|
"""
|
||||||
|
end_time = time.time() + self.LONG_WAIT_IN_SEC
|
||||||
|
while time.time() <= end_time:
|
||||||
|
if zip_path.exists():
|
||||||
|
return True
|
||||||
|
time.sleep(1) # wait so we don't waste sys resources
|
||||||
|
return False
|
100
.github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py
vendored
Normal file
100
.github/scripts/build_assets/selenium_runner/PeekSeleniumRunner.py
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
from typing import List
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from build_assets.selenium_runner.SeleniumRunner import SeleniumRunner
|
||||||
|
from build_assets.selenium_runner.enums import IcomoonPage, IcomoonAlerts
|
||||||
|
|
||||||
|
class PeekSeleniumRunner(SeleniumRunner):
|
||||||
|
def peek(self, svgs: List[str], screenshot_folder: str):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
: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(svgs, screenshot_folder)
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def peek_svgs(self, svgs: List[str], screenshot_folder: str):
|
||||||
|
"""
|
||||||
|
Peek at the SVGs provided in svgs. This will look at how Icomoon
|
||||||
|
interprets the SVGs as a font.
|
||||||
|
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
||||||
|
:param screenshot_folder: the name of the screenshot_folder.
|
||||||
|
:return an array of svgs with strokes as strings. These show which icon
|
||||||
|
contains stroke.
|
||||||
|
"""
|
||||||
|
print("Peeking SVGs...")
|
||||||
|
|
||||||
|
import_btn = self.driver.find_element_by_css_selector(
|
||||||
|
SeleniumRunner.GENERAL_IMPORT_BUTTON_CSS
|
||||||
|
)
|
||||||
|
|
||||||
|
svgs_with_strokes = []
|
||||||
|
for i in range(len(svgs)):
|
||||||
|
import_btn.send_keys(svgs[i])
|
||||||
|
print(f"Uploaded {svgs[i]}")
|
||||||
|
|
||||||
|
alert = self.test_for_possible_alert(self.SHORT_WAIT_IN_SEC)
|
||||||
|
if alert == None:
|
||||||
|
pass # all good
|
||||||
|
elif alert == IcomoonAlerts.STROKES_GET_IGNORED_WARNING:
|
||||||
|
print(f"- This icon contains strokes: {svgs[i]}")
|
||||||
|
svg = Path(svgs[i])
|
||||||
|
svgs_with_strokes.append(f"- {svg.name}")
|
||||||
|
self.click_alert_button(self.ALERTS[alert]["buttons"]["DISMISS"])
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unexpected alert found: {alert}")
|
||||||
|
|
||||||
|
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...")
|
||||||
|
return svgs_with_strokes
|
||||||
|
|
||||||
|
def peek_icons(self, svgs: List[str], screenshot_folder: str):
|
||||||
|
"""
|
||||||
|
Peek at the icon versions of the SVGs that were uploaded.
|
||||||
|
:param screenshot_folder: the name of the screenshot_folder.
|
||||||
|
"""
|
||||||
|
print("Begin peeking at the icons...")
|
||||||
|
# ensure all icons in the set is selected.
|
||||||
|
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)
|
||||||
|
if alert == None:
|
||||||
|
pass # all good
|
||||||
|
elif alert == IcomoonAlerts.DESELECT_ICONS_CONTAINING_STROKES:
|
||||||
|
self.click_alert_button(self.ALERTS[alert]["buttons"]["CONTINUE"])
|
||||||
|
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 downward so we get the oldest icon first
|
||||||
|
len_ = len(svgs)
|
||||||
|
for i in range(len_, 0, -1):
|
||||||
|
xpath = f'//*[@id="glyphSet0"]/div[{i}]'
|
||||||
|
icon_div = self.driver.find_element_by_xpath(xpath)
|
||||||
|
|
||||||
|
# crop the div out from the screenshot
|
||||||
|
icon_screenshot = str(
|
||||||
|
Path(screenshot_folder, f"new_icon_{len_ - i}.png").resolve()
|
||||||
|
)
|
||||||
|
icon_div.screenshot(icon_screenshot)
|
||||||
|
|
||||||
|
print("Finished peeking the icons...")
|
315
.github/scripts/build_assets/selenium_runner/SeleniumRunner.py
vendored
Normal file
315
.github/scripts/build_assets/selenium_runner/SeleniumRunner.py
vendored
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||||
|
from selenium.webdriver.firefox.options import Options
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException
|
||||||
|
|
||||||
|
from build_assets.selenium_runner.enums import IcomoonOptionState, IcomoonPage, IcomoonAlerts
|
||||||
|
|
||||||
|
|
||||||
|
class SeleniumRunner:
|
||||||
|
"""
|
||||||
|
A runner that upload and download Icomoon resources using Selenium.
|
||||||
|
The WebDriver will use Firefox.
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
The long wait time for the driver in seconds.
|
||||||
|
"""
|
||||||
|
LONG_WAIT_IN_SEC = 25
|
||||||
|
|
||||||
|
"""
|
||||||
|
The medium wait time for the driver in seconds.
|
||||||
|
"""
|
||||||
|
MED_WAIT_IN_SEC = 6
|
||||||
|
|
||||||
|
"""
|
||||||
|
The short wait time for the driver in seconds.
|
||||||
|
"""
|
||||||
|
SHORT_WAIT_IN_SEC = 2.5
|
||||||
|
|
||||||
|
"""
|
||||||
|
The short wait time for the driver in seconds.
|
||||||
|
"""
|
||||||
|
BRIEF_WAIT_IN_SEC = 0.6
|
||||||
|
|
||||||
|
"""
|
||||||
|
The Icomoon Url.
|
||||||
|
"""
|
||||||
|
ICOMOON_URL = "https://icomoon.io/app/#/select"
|
||||||
|
|
||||||
|
"""
|
||||||
|
General import button CSS for Icomoon site.
|
||||||
|
"""
|
||||||
|
GENERAL_IMPORT_BUTTON_CSS = "div#file input[type=file]"
|
||||||
|
|
||||||
|
"""
|
||||||
|
Set import button CSS for Icomoon site.
|
||||||
|
"""
|
||||||
|
SET_IMPORT_BUTTON_CSS = "li.file input[type=file]"
|
||||||
|
|
||||||
|
"""
|
||||||
|
The CSS of the tool bar options. There are more but
|
||||||
|
these are the ones that we actually use.
|
||||||
|
"""
|
||||||
|
TOOLBAR_OPTIONS_CSS = {
|
||||||
|
IcomoonOptionState.SELECT: "div.btnBar button i.icon-select",
|
||||||
|
IcomoonOptionState.EDIT: "div.btnBar button i.icon-edit"
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The URL to go to different pages within the Icomoon domain.
|
||||||
|
There are more but these are the ones that we actually use.
|
||||||
|
"""
|
||||||
|
PAGES_URL = {
|
||||||
|
IcomoonPage.SELECTION: ICOMOON_URL,
|
||||||
|
IcomoonPage.GENERATE_FONT: ICOMOON_URL + "/font"
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The different types of alerts that this workflow will encounter.
|
||||||
|
It contains part of the text in the actual alert and buttons
|
||||||
|
available to press. It's up to the user to know what button to
|
||||||
|
press for which alert.
|
||||||
|
"""
|
||||||
|
ALERTS = {
|
||||||
|
IcomoonAlerts.STROKES_GET_IGNORED_WARNING: {
|
||||||
|
"text": "Strokes get ignored when generating fonts or CSH files.",
|
||||||
|
"buttons": {
|
||||||
|
"DISMISS": "Dismiss",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IcomoonAlerts.REPLACE_OR_REIMPORT_ICON : {
|
||||||
|
"text": "Replace existing icons?",
|
||||||
|
"buttons": {
|
||||||
|
"REPLACE": "Replace",
|
||||||
|
"REIMPORT": "Reimport"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IcomoonAlerts.DESELECT_ICONS_CONTAINING_STROKES: {
|
||||||
|
"text": "Strokes get ignored when generating fonts.",
|
||||||
|
"buttons": {
|
||||||
|
"DESELECT": "Deselect",
|
||||||
|
"CONTINUE": "Continue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, download_path: str,
|
||||||
|
geckodriver_path: str, headless: bool):
|
||||||
|
"""
|
||||||
|
Create a SeleniumRunner object.
|
||||||
|
:param download_path: the location where you want to download
|
||||||
|
the icomoon.zip to.
|
||||||
|
:param geckodriver_path: the path to the firefox executable.
|
||||||
|
:param headless: whether to run browser in headless (no UI) mode.
|
||||||
|
"""
|
||||||
|
self.driver = None
|
||||||
|
# default values when we open Icomoon
|
||||||
|
self.current_option_state = IcomoonOptionState.SELECT
|
||||||
|
self.current_page = IcomoonPage.SELECTION
|
||||||
|
self.set_browser_options(download_path, geckodriver_path, headless)
|
||||||
|
|
||||||
|
def set_browser_options(self, download_path: str, geckodriver_path: str,
|
||||||
|
headless: bool):
|
||||||
|
"""
|
||||||
|
Build the WebDriver with Firefox Options allowing downloads and
|
||||||
|
set download to download_path.
|
||||||
|
:param download_path: the location where you want to download
|
||||||
|
:param geckodriver_path: the path to the firefox executable.
|
||||||
|
the icomoon.zip to.
|
||||||
|
:param headless: whether to run browser in headless (no UI) mode.
|
||||||
|
|
||||||
|
:raises AssertionError: if the page title does not contain
|
||||||
|
"IcoMoon App".
|
||||||
|
"""
|
||||||
|
options = Options()
|
||||||
|
allowed_mime_types = "application/zip, application/gzip, application/octet-stream"
|
||||||
|
# disable prompt to download from Firefox
|
||||||
|
options.set_preference("browser.helperApps.neverAsk.saveToDisk", allowed_mime_types)
|
||||||
|
options.set_preference("browser.helperApps.neverAsk.openFile", allowed_mime_types)
|
||||||
|
|
||||||
|
# set the default download path to downloadPath
|
||||||
|
options.set_preference("browser.download.folderList", 2)
|
||||||
|
options.set_preference("browser.download.dir", download_path)
|
||||||
|
options.headless = headless
|
||||||
|
|
||||||
|
print("Activating browser client...")
|
||||||
|
self.driver = WebDriver(options=options, executable_path=geckodriver_path)
|
||||||
|
self.driver.get(self.ICOMOON_URL)
|
||||||
|
assert "IcoMoon App" in self.driver.title
|
||||||
|
# wait until the whole web page is loaded by testing the hamburger input
|
||||||
|
WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
||||||
|
ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]"))
|
||||||
|
)
|
||||||
|
print("Accessed icomoon.io")
|
||||||
|
|
||||||
|
def switch_toolbar_option(self, option: IcomoonOptionState):
|
||||||
|
"""
|
||||||
|
Switch the toolbar option to the option argument.
|
||||||
|
:param option: an option from the toolbar of Icomoon.
|
||||||
|
"""
|
||||||
|
if self.current_option_state == option:
|
||||||
|
return
|
||||||
|
|
||||||
|
option_btn = self.driver.find_element_by_css_selector(
|
||||||
|
SeleniumRunner.TOOLBAR_OPTIONS_CSS[option]
|
||||||
|
)
|
||||||
|
option_btn.click()
|
||||||
|
self.current_option_state = option
|
||||||
|
|
||||||
|
def click_hamburger_input(self):
|
||||||
|
"""
|
||||||
|
Click the hamburger input until the pop up menu appears. This
|
||||||
|
method is needed because sometimes, we need to click the hamburger
|
||||||
|
input two times before the menu appears.
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
top_set_hamburger_input_xpath = '//*[@id="setH2"]/button[1]/i'
|
||||||
|
hamburger_input = self.driver.find_element_by_xpath(
|
||||||
|
top_set_hamburger_input_xpath
|
||||||
|
)
|
||||||
|
|
||||||
|
menu_appear_callback = ec.element_to_be_clickable(
|
||||||
|
(By.CSS_SELECTOR, "h1 ul.menuList2")
|
||||||
|
)
|
||||||
|
|
||||||
|
while not menu_appear_callback(self.driver):
|
||||||
|
hamburger_input.click()
|
||||||
|
|
||||||
|
def test_for_possible_alert(self, wait_period: float) -> IcomoonAlerts:
|
||||||
|
"""
|
||||||
|
Test for the possible alerts that might appear. Return the
|
||||||
|
type of alert if one shows up.
|
||||||
|
:param wait_period: the wait period for the possible alert
|
||||||
|
in seconds.
|
||||||
|
:return: an IcomoonAlerts enum representing the alert that was found.
|
||||||
|
Else, return None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
overlay_div = WebDriverWait(self.driver, wait_period, 0.15).until(
|
||||||
|
ec.element_to_be_clickable(
|
||||||
|
(By.XPATH, "//div[@class='overlay']"))
|
||||||
|
)
|
||||||
|
alert_message = overlay_div.text
|
||||||
|
for alert in self.ALERTS.keys():
|
||||||
|
if self.ALERTS[alert]["text"] in alert_message:
|
||||||
|
return alert
|
||||||
|
|
||||||
|
return IcomoonAlerts.UNKNOWN
|
||||||
|
except SeleniumTimeoutException:
|
||||||
|
return None # nothing found => everything is good
|
||||||
|
|
||||||
|
def click_alert_button(self, btn_text: str):
|
||||||
|
"""
|
||||||
|
Click the button in the alert that matches the button text.
|
||||||
|
:param btn_text: the text that the alert's button will have.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
button = WebDriverWait(self.driver, self.BRIEF_WAIT_IN_SEC, 0.15).until(
|
||||||
|
ec.element_to_be_clickable(
|
||||||
|
(By.XPATH, f"//div[@class='overlay']//button[text()='{btn_text}']"))
|
||||||
|
)
|
||||||
|
button.click()
|
||||||
|
except SeleniumTimeoutException:
|
||||||
|
return None # nothing found => everything is good
|
||||||
|
|
||||||
|
def edit_svg(self, screenshot_folder: str=None, index: int=None):
|
||||||
|
"""
|
||||||
|
Edit the SVG. This include removing the colors and take a
|
||||||
|
snapshot if needed.
|
||||||
|
:param screenshot_folder: a string or Path object. Point to
|
||||||
|
where we store the screenshots. If truthy, take a screenshot
|
||||||
|
and save it here.
|
||||||
|
:param index: the index of the icon in its containing list.
|
||||||
|
Used to differentiate the screenshots. Must be truthy if
|
||||||
|
screenshot_folder is a truthy value.
|
||||||
|
"""
|
||||||
|
self.switch_toolbar_option(IcomoonOptionState.EDIT)
|
||||||
|
self.click_latest_icons_in_top_set(1)
|
||||||
|
|
||||||
|
# strip the colors from the SVG.
|
||||||
|
# This is because some SVG have colors in them and we don't want to
|
||||||
|
# force contributors to remove them in case people want the colored SVGs.
|
||||||
|
# The color removal is also necessary so that the Icomoon-generated
|
||||||
|
# icons fit within one font symbol/ligiature.
|
||||||
|
try:
|
||||||
|
color_tab = WebDriverWait(self.driver, self.BRIEF_WAIT_IN_SEC).until(
|
||||||
|
ec.element_to_be_clickable((By.CSS_SELECTOR, "div.overlayWindow i.icon-droplet"))
|
||||||
|
)
|
||||||
|
color_tab.click()
|
||||||
|
|
||||||
|
remove_color_btn = self.driver \
|
||||||
|
.find_element_by_css_selector("div.overlayWindow i.icon-droplet-cross")
|
||||||
|
remove_color_btn.click()
|
||||||
|
except SeleniumTimeoutException:
|
||||||
|
pass # do nothing cause sometimes, the color tab doesn't appear in the site
|
||||||
|
|
||||||
|
if screenshot_folder != None and index != None:
|
||||||
|
edit_screen_selector = "div.overlay div.overlayWindow"
|
||||||
|
screenshot_path = str(
|
||||||
|
Path(screenshot_folder, f"new_svg_{index}.png").resolve()
|
||||||
|
)
|
||||||
|
edit_screen = self.driver.find_element_by_css_selector(
|
||||||
|
edit_screen_selector)
|
||||||
|
edit_screen.screenshot(screenshot_path)
|
||||||
|
print("Took screenshot of svg and saved it at " + screenshot_path)
|
||||||
|
|
||||||
|
close_btn = self.driver \
|
||||||
|
.find_element_by_css_selector("div.overlayWindow i.icon-close")
|
||||||
|
close_btn.click()
|
||||||
|
|
||||||
|
def click_latest_icons_in_top_set(self, amount: int):
|
||||||
|
"""
|
||||||
|
Click on the latest icons in the top set based on the amount passed in.
|
||||||
|
This is state option agnostic (doesn't care if it's in SELECT or EDIT mode).
|
||||||
|
:param amount: the amount of icons to click on from top left of the top
|
||||||
|
set. Must be > 0.
|
||||||
|
"""
|
||||||
|
icon_base_xpath = '//div[@id="set0"]//mi-box[{}]//div'
|
||||||
|
for i in range(1, amount + 1):
|
||||||
|
icon_xpath = icon_base_xpath.format(i)
|
||||||
|
latest_icon = self.driver.find_element_by_xpath(icon_xpath)
|
||||||
|
latest_icon.click()
|
||||||
|
|
||||||
|
def select_all_icons_in_top_set(self):
|
||||||
|
"""
|
||||||
|
Select all the svgs in the top most (latest) set.
|
||||||
|
"""
|
||||||
|
self.click_hamburger_input()
|
||||||
|
select_all_button = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
||||||
|
ec.element_to_be_clickable((By.XPATH, "//button[text()='Select All']"))
|
||||||
|
)
|
||||||
|
select_all_button.click()
|
||||||
|
|
||||||
|
def deselect_all_icons_in_top_set(self):
|
||||||
|
"""
|
||||||
|
Select all the svgs in the top most (latest) set.
|
||||||
|
"""
|
||||||
|
self.click_hamburger_input()
|
||||||
|
select_all_button = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
||||||
|
ec.element_to_be_clickable((By.XPATH, "//button[text()='Deselect']"))
|
||||||
|
)
|
||||||
|
select_all_button.click()
|
||||||
|
|
||||||
|
def go_to_page(self, page: IcomoonPage):
|
||||||
|
"""
|
||||||
|
Go to the specified page in Icomoon. This used the URL rather than UI
|
||||||
|
elements due to the inconsistent UI rendering.
|
||||||
|
:param page: a valid page that can be accessed in Icomoon.
|
||||||
|
"""
|
||||||
|
if self.current_page == page:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.driver.get(self.PAGES_URL[page])
|
||||||
|
self.current_page = page
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Close the SeleniumRunner instance.
|
||||||
|
"""
|
||||||
|
print("Closing down SeleniumRunner...")
|
||||||
|
self.driver.quit()
|
28
.github/scripts/build_assets/selenium_runner/enums.py
vendored
Normal file
28
.github/scripts/build_assets/selenium_runner/enums.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class IcomoonOptionState(Enum):
|
||||||
|
"""
|
||||||
|
The state of the Icomoon toolbar options
|
||||||
|
"""
|
||||||
|
SELECT = 0,
|
||||||
|
EDIT = 1
|
||||||
|
|
||||||
|
|
||||||
|
class IcomoonPage(Enum):
|
||||||
|
"""
|
||||||
|
The available pages on the Icomoon website.
|
||||||
|
"""
|
||||||
|
SELECTION = 0,
|
||||||
|
GENERATE_FONT = 1
|
||||||
|
|
||||||
|
|
||||||
|
class IcomoonAlerts(Enum):
|
||||||
|
"""
|
||||||
|
The alerts that Icomoon displayed to the user. There
|
||||||
|
could be more but these are the ones we usually see.
|
||||||
|
"""
|
||||||
|
STROKES_GET_IGNORED_WARNING = 0,
|
||||||
|
REPLACE_OR_REIMPORT_ICON = 1,
|
||||||
|
DESELECT_ICONS_CONTAINING_STROKES = 2,
|
||||||
|
UNKNOWN = 3
|
12
.github/scripts/build_assets/util.py
vendored
12
.github/scripts/build_assets/util.py
vendored
@@ -56,9 +56,13 @@ def find_object_added_in_pr(icons: List[dict], pr_title: str):
|
|||||||
:raise If no object can be found, raise an Exception.
|
:raise If no object can be found, raise an Exception.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
pattern = re.compile(r"(?<=^new icon: )\w+ (?=\(.+\))", re.I)
|
pattern = re.compile(r"(?<=^new icon: )\w+ (?=\(.+\))|(?<=^update icon: )\w+ (?=\(.+\))", re.I)
|
||||||
icon_name = pattern.findall(pr_title)[0].lower().strip() # should only have one match
|
icon_name_index = 0
|
||||||
|
icon_name = pattern.findall(pr_title)[icon_name_index].lower().strip() # should only have one match
|
||||||
icon = [icon for icon in icons if icon["name"] == icon_name][0]
|
icon = [icon for icon in icons if icon["name"] == icon_name][0]
|
||||||
return icon
|
return icon
|
||||||
except IndexError: # there are no match in the findall()
|
except IndexError as e: # there are no match in the findall()
|
||||||
raise Exception("Couldn't find an icon matching the name in the PR title.")
|
print(e)
|
||||||
|
message = "util.find_object_added_in_pr: Couldn't find an icon matching the name in the PR title.\n" \
|
||||||
|
f"PR title is: '{pr_title}'"
|
||||||
|
raise Exception(message)
|
||||||
|
39
.github/scripts/get_release_message.py
vendored
39
.github/scripts/get_release_message.py
vendored
@@ -1,39 +0,0 @@
|
|||||||
import requests
|
|
||||||
from build_assets import arg_getters, api_handler, util
|
|
||||||
import re
|
|
||||||
|
|
||||||
def main():
|
|
||||||
try:
|
|
||||||
print("Please wait a few seconds...")
|
|
||||||
args = arg_getters.get_release_message_args()
|
|
||||||
|
|
||||||
# fetch first page by default
|
|
||||||
data = api_handler.get_merged_pull_reqs_since_last_release(args.token)
|
|
||||||
newIcons = []
|
|
||||||
features = []
|
|
||||||
|
|
||||||
print("Parsing through the pull requests")
|
|
||||||
for pullData in data:
|
|
||||||
authors = api_handler.find_all_authors(pullData, args.token)
|
|
||||||
markdown = f"- [{pullData['title']}]({pullData['html_url']}) by {authors}."
|
|
||||||
|
|
||||||
if api_handler.is_feature_icon(pullData):
|
|
||||||
newIcons.append(markdown)
|
|
||||||
else:
|
|
||||||
features.append(markdown)
|
|
||||||
|
|
||||||
print("Constructing message")
|
|
||||||
thankYou = "A huge thanks to all our maintainers and contributors for making this release possible!"
|
|
||||||
iconTitle = f"**{len(newIcons)} New Icons**"
|
|
||||||
featureTitle = f"**{len(features)} New Features**"
|
|
||||||
finalString = "{0}\n\n {1}\n{2}\n\n {3}\n{4}".format(thankYou,
|
|
||||||
iconTitle, "\n".join(newIcons), featureTitle, "\n".join(features))
|
|
||||||
|
|
||||||
print("--------------Here is the build message--------------\n", finalString)
|
|
||||||
print("Script finished")
|
|
||||||
except Exception as e:
|
|
||||||
util.exit_with_err(e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
61
.github/scripts/icomoon_build.py
vendored
61
.github/scripts/icomoon_build.py
vendored
@@ -9,7 +9,7 @@ from typing import List, Dict
|
|||||||
|
|
||||||
# pycharm complains that build_assets is an unresolved ref
|
# pycharm complains that build_assets is an unresolved ref
|
||||||
# don't worry about it, the script still runs
|
# don't worry about it, the script still runs
|
||||||
from build_assets.SeleniumRunner import SeleniumRunner
|
from build_assets.selenium_runner.BuildSeleniumRunner import BuildSeleniumRunner
|
||||||
from build_assets import filehandler, arg_getters, util, api_handler
|
from build_assets import filehandler, arg_getters, util, api_handler
|
||||||
|
|
||||||
|
|
||||||
@@ -26,24 +26,29 @@ def main():
|
|||||||
|
|
||||||
print(f"There are {len(new_icons)} icons to be build. Here are they:", *new_icons, sep = "\n")
|
print(f"There are {len(new_icons)} icons to be build. Here are they:", *new_icons, sep = "\n")
|
||||||
|
|
||||||
print("Begin optimizing files")
|
print("Begin optimizing files...")
|
||||||
optimize_svgs(new_icons, args.icons_folder_path)
|
optimize_svgs(new_icons, args.icons_folder_path)
|
||||||
|
|
||||||
print("Updating the icomoon json")
|
print("Updating the icomoon json...")
|
||||||
update_icomoon_json(new_icons, args.icomoon_json_path)
|
update_icomoon_json(new_icons, args.icomoon_json_path)
|
||||||
|
|
||||||
|
print("Start the building icons process...")
|
||||||
icon_svgs = filehandler.get_svgs_paths(
|
icon_svgs = filehandler.get_svgs_paths(
|
||||||
new_icons, args.icons_folder_path, icon_versions_only=True)
|
new_icons, args.icons_folder_path, icon_versions_only=True)
|
||||||
runner = SeleniumRunner(args.download_path,
|
|
||||||
args.geckodriver_path, args.headless)
|
|
||||||
runner.upload_icomoon(args.icomoon_json_path)
|
|
||||||
runner.upload_svgs(icon_svgs)
|
|
||||||
|
|
||||||
zip_name = "devicon-v1.0.zip"
|
zip_name = "devicon-v1.0.zip"
|
||||||
zip_path = Path(args.download_path, zip_name)
|
zip_path = Path(args.download_path, zip_name)
|
||||||
runner.download_icomoon_fonts(zip_path)
|
screenshot_folder = filehandler.create_screenshot_folder("./")
|
||||||
|
runner = BuildSeleniumRunner(args.download_path,
|
||||||
|
args.geckodriver_path, args.headless)
|
||||||
|
runner.build_icons(args.icomoon_json_path, zip_path,
|
||||||
|
icon_svgs, screenshot_folder)
|
||||||
|
|
||||||
filehandler.extract_files(str(zip_path), args.download_path)
|
filehandler.extract_files(str(zip_path), args.download_path)
|
||||||
filehandler.rename_extracted_files(args.download_path)
|
filehandler.rename_extracted_files(args.download_path)
|
||||||
|
|
||||||
|
print("Creating the release message by querying the GitHub API...")
|
||||||
|
get_release_message(args.token)
|
||||||
|
|
||||||
print("Task completed.")
|
print("Task completed.")
|
||||||
except TimeoutException as e:
|
except TimeoutException as e:
|
||||||
util.exit_with_err("Selenium Time Out Error: \n" + str(e))
|
util.exit_with_err("Selenium Time Out Error: \n" + str(e))
|
||||||
@@ -60,6 +65,8 @@ def get_icons_for_building(icomoon_json_path: str, devicon_json_path: str, token
|
|||||||
:param icomoon_json_path - the path to the `icomoon.json`.
|
:param icomoon_json_path - the path to the `icomoon.json`.
|
||||||
:param devicon_json_path - the path to the `devicon.json`.
|
:param devicon_json_path - the path to the `devicon.json`.
|
||||||
:param token - the token to access the GitHub API.
|
:param token - the token to access the GitHub API.
|
||||||
|
:return a list of dict containing info on the icons. These are
|
||||||
|
from the `devicon.json`.
|
||||||
"""
|
"""
|
||||||
devicon_json = filehandler.get_json_file_content(devicon_json_path)
|
devicon_json = filehandler.get_json_file_content(devicon_json_path)
|
||||||
pull_reqs = api_handler.get_merged_pull_reqs_since_last_release(token)
|
pull_reqs = api_handler.get_merged_pull_reqs_since_last_release(token)
|
||||||
@@ -72,6 +79,7 @@ def get_icons_for_building(icomoon_json_path: str, devicon_json_path: str, token
|
|||||||
new_icons.append(filtered_icon)
|
new_icons.append(filtered_icon)
|
||||||
|
|
||||||
# get any icons that might not have been found by the API
|
# get any icons that might not have been found by the API
|
||||||
|
# sometimes happen due to the PR being opened before the latest build release
|
||||||
new_icons_from_devicon_json = filehandler.find_new_icons_in_devicon_json(
|
new_icons_from_devicon_json = filehandler.find_new_icons_in_devicon_json(
|
||||||
devicon_json_path, icomoon_json_path)
|
devicon_json_path, icomoon_json_path)
|
||||||
|
|
||||||
@@ -136,5 +144,40 @@ def find_icomoon_icon_not_in_new_icons(icomoon_icon: Dict, new_icons: List, mess
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_release_message(token):
|
||||||
|
"""
|
||||||
|
Get the release message for the latest build and write
|
||||||
|
the result in a file.
|
||||||
|
:param token: the GitHub API token to access the API.
|
||||||
|
"""
|
||||||
|
# fetch first page by default
|
||||||
|
data = api_handler.get_merged_pull_reqs_since_last_release(token)
|
||||||
|
newIcons = []
|
||||||
|
features = []
|
||||||
|
|
||||||
|
print("Parsing through the pull requests...")
|
||||||
|
for pullData in data:
|
||||||
|
authors = api_handler.find_all_authors(pullData, token)
|
||||||
|
markdown = f"- [{pullData['title']}]({pullData['html_url']}) by {authors}."
|
||||||
|
|
||||||
|
if api_handler.is_feature_icon(pullData):
|
||||||
|
newIcons.append(markdown)
|
||||||
|
else:
|
||||||
|
features.append(markdown)
|
||||||
|
|
||||||
|
print("Constructing message...")
|
||||||
|
thankYou = "A huge thanks to all our maintainers and contributors for making this release possible!"
|
||||||
|
iconTitle = f"**{len(newIcons)} New Icons**"
|
||||||
|
featureTitle = f"**{len(features)} New Features**"
|
||||||
|
finalString = "{0}\n\n {1}\n{2}\n\n {3}\n{4}".format(thankYou,
|
||||||
|
iconTitle, "\n".join(newIcons),
|
||||||
|
featureTitle, "\n".join(features))
|
||||||
|
|
||||||
|
print("--------------Here is the build message--------------\n", finalString)
|
||||||
|
release_message_path = "./release_message.txt"
|
||||||
|
filehandler.write_to_file(release_message_path, finalString)
|
||||||
|
print("Script finished")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
21
.github/scripts/icomoon_peek.py
vendored
21
.github/scripts/icomoon_peek.py
vendored
@@ -1,11 +1,4 @@
|
|||||||
from typing import List
|
from build_assets.selenium_runner.PeekSeleniumRunner import PeekSeleniumRunner
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from selenium.common.exceptions import TimeoutException
|
|
||||||
|
|
||||||
# pycharm complains that build_assets is an unresolved ref
|
|
||||||
# don't worry about it, the script still runs
|
|
||||||
from build_assets.SeleniumRunner import SeleniumRunner
|
|
||||||
from build_assets import filehandler, arg_getters
|
from build_assets import filehandler, arg_getters
|
||||||
from build_assets import util
|
from build_assets import util
|
||||||
|
|
||||||
@@ -21,18 +14,22 @@ def main():
|
|||||||
check_devicon_object(filtered_icon)
|
check_devicon_object(filtered_icon)
|
||||||
print("Icon being checked:", filtered_icon, sep = "\n", end='\n\n')
|
print("Icon being checked:", filtered_icon, sep = "\n", end='\n\n')
|
||||||
|
|
||||||
runner = SeleniumRunner(args.download_path, args.geckodriver_path, args.headless)
|
runner = PeekSeleniumRunner(args.download_path, args.geckodriver_path, args.headless)
|
||||||
svgs = filehandler.get_svgs_paths([filtered_icon], args.icons_folder_path, True)
|
svgs = filehandler.get_svgs_paths([filtered_icon], args.icons_folder_path, True)
|
||||||
screenshot_folder = filehandler.create_screenshot_folder("./")
|
screenshot_folder = filehandler.create_screenshot_folder("./")
|
||||||
runner.upload_svgs(svgs, screenshot_folder)
|
svgs_with_strokes = runner.peek(svgs, screenshot_folder)
|
||||||
print("Task completed.")
|
print("Task completed.")
|
||||||
|
|
||||||
# no errors, do this so upload-artifact won't fail
|
message = ""
|
||||||
filehandler.write_to_file("./err_messages.txt", "0")
|
if svgs_with_strokes != []:
|
||||||
|
svgs_str = "\n\n".join(svgs_with_strokes)
|
||||||
|
message = "\n### WARNING -- Strokes detected in the following SVGs:\n" + svgs_str + "\n"
|
||||||
|
filehandler.write_to_file("./err_messages.txt", message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
filehandler.write_to_file("./err_messages.txt", str(e))
|
filehandler.write_to_file("./err_messages.txt", str(e))
|
||||||
util.exit_with_err(e)
|
util.exit_with_err(e)
|
||||||
finally:
|
finally:
|
||||||
|
if runner is not None:
|
||||||
runner.close()
|
runner.close()
|
||||||
|
|
||||||
|
|
||||||
|
36
.github/workflows/build_icons.yml
vendored
36
.github/workflows/build_icons.yml
vendored
@@ -42,27 +42,45 @@ jobs:
|
|||||||
uses: devicons/public-upload-to-imgur@v2.2.2
|
uses: devicons/public-upload-to-imgur@v2.2.2
|
||||||
if: success()
|
if: success()
|
||||||
with:
|
with:
|
||||||
path: ./new_icons.png
|
# will have "new_icons.png" and "new_svgs.png"
|
||||||
|
# in that order (cause sorted alphabetically)
|
||||||
|
path: ./screenshots/*.png
|
||||||
client_id: ${{secrets.IMGUR_CLIENT_ID}}
|
client_id: ${{secrets.IMGUR_CLIENT_ID}}
|
||||||
|
|
||||||
|
- name: Get the release message from file
|
||||||
|
id: release_message_step
|
||||||
|
uses: juliangruber/read-file-action@v1.0.0
|
||||||
|
with:
|
||||||
|
# taken from icomoon_build.py's get_release_message()
|
||||||
|
path: ./release_message.txt
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: success()
|
if: success()
|
||||||
uses: peter-evans/create-pull-request@v3
|
uses: peter-evans/create-pull-request@v3
|
||||||
env:
|
env:
|
||||||
MESSAGE: |
|
MESSAGE: |
|
||||||
What's up!
|
Hello,
|
||||||
|
|
||||||
I'm Devicon's Build Bot and I just built some new font files and devicon.min.css file.
|
I'm Devicon's Build Bot and I just built some new font files and devicon.min.css file.
|
||||||
|
|
||||||
Here are all the files that were built into icons (the new ones are those without highlight):
|
Here are all the **SVGs** that were uploaded (the new ones are those without highlight):
|
||||||
|
|
||||||

|
{0}
|
||||||
|
|
||||||
|
Here is what they look like as icons:
|
||||||
|
|
||||||
|
{1}
|
||||||
|
|
||||||
The devicon.min.css file contains:
|
The devicon.min.css file contains:
|
||||||
-The icon content
|
-The icon content
|
||||||
-The aliases
|
-The aliases
|
||||||
-The colored classes
|
-The colored classes
|
||||||
|
|
||||||
|
I also compiled a list of new features and icons that were added since last release.
|
||||||
|
```
|
||||||
|
{2}
|
||||||
|
```
|
||||||
|
|
||||||
More information can be found in the GitHub Action logs for this workflow.
|
More information can be found in the GitHub Action logs for this workflow.
|
||||||
|
|
||||||
Adios,
|
Adios,
|
||||||
@@ -71,5 +89,13 @@ jobs:
|
|||||||
branch: 'bot/build-result'
|
branch: 'bot/build-result'
|
||||||
commit-message: 'Built new icons, icomoon.json and devicon.css'
|
commit-message: 'Built new icons, icomoon.json and devicon.css'
|
||||||
title: 'bot:build new icons, icomoon.json and devicon.css'
|
title: 'bot:build new icons, icomoon.json and devicon.css'
|
||||||
body: ${{ format(env.MESSAGE, fromJSON(steps.imgur_step.outputs.imgur_urls)[0] ) }}
|
body: >
|
||||||
|
${{
|
||||||
|
format(
|
||||||
|
env.MESSAGE,
|
||||||
|
fromJSON(steps.imgur_step.outputs.markdown_urls)[1],
|
||||||
|
fromJSON(steps.imgur_step.outputs.markdown_urls)[0],
|
||||||
|
steps.release_message_step.outputs.content
|
||||||
|
)
|
||||||
|
}}
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
|
23
.github/workflows/get_release_message.yml
vendored
23
.github/workflows/get_release_message.yml
vendored
@@ -1,23 +0,0 @@
|
|||||||
name: Get Release Message
|
|
||||||
on: workflow_dispatch
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Get features since last release
|
|
||||||
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
|
|
77
.github/workflows/post_peek_screenshot.yml
vendored
77
.github/workflows/post_peek_screenshot.yml
vendored
@@ -39,6 +39,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ./err_messages/err_messages.txt
|
path: ./err_messages/err_messages.txt
|
||||||
|
|
||||||
|
# using this trigger allowed us to access secrets
|
||||||
|
- name: Upload screenshot of the SVGs gotten from the artifacts
|
||||||
|
id: svgs_overview_img_step
|
||||||
|
if: env.PEEK_STATUS == 'success' && success()
|
||||||
|
uses: devicons/public-upload-to-imgur@v2.2.2
|
||||||
|
with:
|
||||||
|
path: ./screenshots/new_svgs.png
|
||||||
|
client_id: ${{secrets.IMGUR_CLIENT_ID}}
|
||||||
|
|
||||||
|
- name: Upload zoomed in screenshot of the SVGs gotten from the artifacts
|
||||||
|
id: svgs_detailed_img_step
|
||||||
|
uses: devicons/public-upload-to-imgur@v2.2.2
|
||||||
|
if: env.PEEK_STATUS == 'success' && success()
|
||||||
|
with:
|
||||||
|
path: ./screenshots/new_svg_*.png
|
||||||
|
client_id: ${{secrets.IMGUR_CLIENT_ID}}
|
||||||
|
|
||||||
- name: Upload screenshot of the newly made icons gotten from the artifacts
|
- name: Upload screenshot of the newly made icons gotten from the artifacts
|
||||||
id: icons_overview_img_step
|
id: icons_overview_img_step
|
||||||
@@ -53,7 +69,7 @@ jobs:
|
|||||||
uses: devicons/public-upload-to-imgur@v2.2.2
|
uses: devicons/public-upload-to-imgur@v2.2.2
|
||||||
if: env.PEEK_STATUS == 'success' && success()
|
if: env.PEEK_STATUS == 'success' && success()
|
||||||
with:
|
with:
|
||||||
path: ./screenshots/screenshot_*.png
|
path: ./screenshots/new_icon_*.png
|
||||||
client_id: ${{secrets.IMGUR_CLIENT_ID}}
|
client_id: ${{secrets.IMGUR_CLIENT_ID}}
|
||||||
|
|
||||||
- name: Comment on the PR about the result - Success
|
- name: Comment on the PR about the result - Success
|
||||||
@@ -64,19 +80,32 @@ jobs:
|
|||||||
Hi there,
|
Hi there,
|
||||||
|
|
||||||
I'm Devicons' Peek Bot and I just peeked at the icons that you wanted to add using [icomoon.io](https://icomoon.io/app/#/select).
|
I'm Devicons' Peek Bot and I just peeked at the icons that you wanted to add using [icomoon.io](https://icomoon.io/app/#/select).
|
||||||
Here is the result below (top left):
|
|
||||||
|
|
||||||
{0}
|
{0}
|
||||||
|
Here are the SVGs as intepreted by Icomoon when we upload the files:
|
||||||
Here are the zoomed-in screenshots of the added icons:
|
|
||||||
{1}
|
{1}
|
||||||
|
|
||||||
|
Here are the zoomed-in screenshots of the added icons as **SVGs**. This is how Icomoon intepret the uploaded SVGs:
|
||||||
|
{2}
|
||||||
|
|
||||||
|
Here are the icons that will be generated by Icomoon:
|
||||||
|
{3}
|
||||||
|
|
||||||
|
Here are the zoomed-in screenshots of the added icons as **icons**. This is what the font will look like:
|
||||||
|
{4}
|
||||||
|
|
||||||
|
You can click on the pictures and zoom on them if needed.
|
||||||
|
|
||||||
|
The maintainers will now check for:
|
||||||
|
1. The number of Glyphs matches the number of SVGs that were selected.
|
||||||
|
2. The icons (second group of pictures) look the same as the SVGs (first group of pictures).
|
||||||
|
3. The icons are of high quality (legible, matches the official logo, etc.)
|
||||||
|
|
||||||
|
In case of font issues, it might be caused by Icomoon not accepting strokes in the SVGs. Check this [doc](https://icomoon.io/#faq/importing) for more details and fix the issues as instructed by Icomoon and update this PR once you are done.
|
||||||
|
|
||||||
|
Thank you for contributing to Devicon! I hope that your icons are accepted into the repository.
|
||||||
|
|
||||||
Note: If the images don't show up, it's probably because it has been autodeleted by Imgur after 6 months due to our API choice.
|
Note: If the images don't show up, it's probably because it has been autodeleted by Imgur after 6 months due to our API choice.
|
||||||
|
|
||||||
**The maintainers will now take a look at it and decide whether to merge your PR.**
|
|
||||||
|
|
||||||
Thank you for contributing to Devicon! I hope everything works out and your icons are accepted into the repo.
|
|
||||||
|
|
||||||
Cheers,
|
Cheers,
|
||||||
Peek Bot :blush:
|
Peek Bot :blush:
|
||||||
with:
|
with:
|
||||||
@@ -84,12 +113,19 @@ jobs:
|
|||||||
issue_number: ${{ steps.pr_num_reader.outputs.content }}
|
issue_number: ${{ steps.pr_num_reader.outputs.content }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
body: >
|
body: >
|
||||||
${{ format(env.MESSAGE,
|
${{
|
||||||
|
format(
|
||||||
|
env.MESSAGE,
|
||||||
|
steps.err_message_reader.outputs.content,
|
||||||
|
fromJSON(steps.svgs_overview_img_step.outputs.markdown_urls)[0],
|
||||||
|
join(fromJSON(steps.svgs_detailed_img_step.outputs.markdown_urls), ' '),
|
||||||
fromJSON(steps.icons_overview_img_step.outputs.markdown_urls)[0],
|
fromJSON(steps.icons_overview_img_step.outputs.markdown_urls)[0],
|
||||||
join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), '')) }}
|
join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), ' ')
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
- name: Comment on the PR about the result - Failure
|
- name: Comment on the PR about the result - Failure
|
||||||
if: failure() || env.PEEK_STATUS == 'failure'
|
if: env.PEEK_STATUS == 'failure'
|
||||||
uses: jungwinter/comment@v1 # let us comment on a specific PR
|
uses: jungwinter/comment@v1 # let us comment on a specific PR
|
||||||
env:
|
env:
|
||||||
MESSAGE: |
|
MESSAGE: |
|
||||||
@@ -116,3 +152,20 @@ jobs:
|
|||||||
issue_number: ${{ steps.pr_num_reader.outputs.content }}
|
issue_number: ${{ steps.pr_num_reader.outputs.content }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
body: ${{ format(env.MESSAGE, steps.err_message_reader.outputs.content) }}
|
body: ${{ format(env.MESSAGE, steps.err_message_reader.outputs.content) }}
|
||||||
|
- name: Comment on the PR about the result - Failure
|
||||||
|
if: failure()
|
||||||
|
uses: jungwinter/comment@v1 # let us comment on a specific PR
|
||||||
|
env:
|
||||||
|
MESSAGE: |
|
||||||
|
Hi there,
|
||||||
|
|
||||||
|
I'm Devicons' Peek Bot and we've ran into a problem with the `post_peek_screenshot` workflow.
|
||||||
|
The maintainers will take a look and fix the issue. Please wait for further instructions.
|
||||||
|
|
||||||
|
Thank you,
|
||||||
|
Peek Bot :relaxed:
|
||||||
|
with:
|
||||||
|
type: create
|
||||||
|
issue_number: ${{ steps.pr_num_reader.outputs.content }}
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
body: env.MESSAGE
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ geckodriver.log
|
|||||||
__pycache__
|
__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
new_icons.png
|
new_icons.png
|
||||||
|
screenshots/
|
||||||
|
release_message.txt
|
||||||
|
0
devicon.css → devicon-base.css
Executable file → Normal file
0
devicon.css → devicon-base.css
Executable file → Normal file
@@ -1,11 +1,13 @@
|
|||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const svgmin = require("gulp-svgmin");
|
const svgmin = require("gulp-svgmin");
|
||||||
const sass = require("gulp-sass")(require("sass"));
|
const sass = require("gulp-sass")(require("sass"));
|
||||||
|
const footer = require("gulp-footer");
|
||||||
const yargs = require("yargs");
|
const yargs = require("yargs");
|
||||||
const fsPromise = require("fs").promises;
|
const fsPromise = require("fs").promises;
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
// global const
|
// global const
|
||||||
|
const deviconBaseCSSName = "devicon-base.css"
|
||||||
const deviconJSONName = "devicon.json";
|
const deviconJSONName = "devicon.json";
|
||||||
const aliasSCSSName = "devicon-alias.scss";
|
const aliasSCSSName = "devicon-alias.scss";
|
||||||
const colorsCSSName = "devicon-colors.css";
|
const colorsCSSName = "devicon-colors.css";
|
||||||
@@ -21,7 +23,7 @@ async function createDeviconMinCSS() {
|
|||||||
await createCSSFiles();
|
await createCSSFiles();
|
||||||
|
|
||||||
let deviconMinPath = path.join(__dirname, finalMinSCSSName);
|
let deviconMinPath = path.join(__dirname, finalMinSCSSName);
|
||||||
// recall that devicon-alias.scss imported the devicon.css => don't need
|
// recall that devicon-alias.scss imported the devicon-base.css => don't need
|
||||||
// to reimport that file.
|
// to reimport that file.
|
||||||
const fileContent = `@use "${aliasSCSSName}";@use "${colorsCSSName}";`;
|
const fileContent = `@use "${aliasSCSSName}";@use "${colorsCSSName}";`;
|
||||||
await fsPromise.writeFile(deviconMinPath, fileContent, "utf8");
|
await fsPromise.writeFile(deviconMinPath, fileContent, "utf8");
|
||||||
@@ -59,7 +61,7 @@ async function createCSSFiles() {
|
|||||||
*/
|
*/
|
||||||
function createAliasSCSS(deviconJson) {
|
function createAliasSCSS(deviconJson) {
|
||||||
let statements = deviconJson.map(createAliasStatement).join(" ");
|
let statements = deviconJson.map(createAliasStatement).join(" ");
|
||||||
let sass = `@use "devicon";${statements}`;
|
let sass = `@use "${deviconBaseCSSName}";${statements}`;
|
||||||
let sassPath = path.join(__dirname, aliasSCSSName);
|
let sassPath = path.join(__dirname, aliasSCSSName);
|
||||||
return fsPromise.writeFile(sassPath, sass, "utf8");
|
return fsPromise.writeFile(sassPath, sass, "utf8");
|
||||||
}
|
}
|
||||||
@@ -156,6 +158,7 @@ function optimizeSvg() {
|
|||||||
return gulp
|
return gulp
|
||||||
.src(svgGlob)
|
.src(svgGlob)
|
||||||
.pipe(svgmin(configOptionCallback))
|
.pipe(svgmin(configOptionCallback))
|
||||||
|
.pipe(footer("\n"))
|
||||||
.pipe(
|
.pipe(
|
||||||
gulp.dest(file => {
|
gulp.dest(file => {
|
||||||
return file.base;
|
return file.base;
|
||||||
|
Reference in New Issue
Block a user