1
0
mirror of https://github.com/konpa/devicon.git synced 2025-04-21 05:11:56 +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:
Thomas Bui 2021-08-13 11:51:22 -07:00 committed by GitHub
parent e5aa8a9cad
commit 08aa325a84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 822 additions and 408 deletions

View File

@ -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()

View File

@ -2,10 +2,38 @@ import requests
import sys
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):
"""
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.
:param token, a GitHub API token.
:param page, the page number.
@ -71,30 +99,3 @@ def find_all_authors(pull_req_data, token):
authors.add(commit["commit"]["author"]["name"])
print(f"This URL didn't have an `author` attribute: {pull_req_data['commits_url']}")
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

View File

@ -91,7 +91,7 @@ def get_icon_svgs_paths(folder_path: Path, icon_info: dict,
for font_version in icon_info["versions"]["font"]:
# if it's an alias, we don't want to make it into an icon
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
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"),
"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:
os.mkdir(screenshot_folder)
except FileExistsError:
print(f"{screenshot_folder} already exist. Script will do nothing.")
print(f"{screenshot_folder} already exist. Not creating new folder.")
finally:
return str(screenshot_folder)

View 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

View 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...")

View 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()

View 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

View File

@ -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.
"""
try:
pattern = re.compile(r"(?<=^new icon: )\w+ (?=\(.+\))", re.I)
icon_name = pattern.findall(pr_title)[0].lower().strip() # should only have one match
icon = [icon for icon in icons if icon["name"] == icon_name][0]
pattern = re.compile(r"(?<=^new icon: )\w+ (?=\(.+\))|(?<=^update icon: )\w+ (?=\(.+\))", re.I)
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]
return icon
except IndexError: # there are no match in the findall()
raise Exception("Couldn't find an icon matching the name in the PR title.")
except IndexError as e: # there are no match in the findall()
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)

View File

@ -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()

View File

@ -9,7 +9,7 @@ from typing import List, Dict
# 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.selenium_runner.BuildSeleniumRunner import BuildSeleniumRunner
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("Begin optimizing files")
print("Begin optimizing files...")
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)
print("Start the building icons process...")
icon_svgs = filehandler.get_svgs_paths(
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_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.rename_extracted_files(args.download_path)
print("Creating the release message by querying the GitHub API...")
get_release_message(args.token)
print("Task completed.")
except TimeoutException as 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 devicon_json_path - the path to the `devicon.json`.
: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)
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)
# 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(
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
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__":
main()

View File

@ -1,11 +1,4 @@
from typing import List
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.selenium_runner.PeekSeleniumRunner import PeekSeleniumRunner
from build_assets import filehandler, arg_getters
from build_assets import util
@ -21,19 +14,23 @@ def main():
check_devicon_object(filtered_icon)
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)
screenshot_folder = filehandler.create_screenshot_folder("./")
runner.upload_svgs(svgs, screenshot_folder)
svgs_with_strokes = runner.peek(svgs, screenshot_folder)
print("Task completed.")
# no errors, do this so upload-artifact won't fail
filehandler.write_to_file("./err_messages.txt", "0")
message = ""
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:
filehandler.write_to_file("./err_messages.txt", str(e))
util.exit_with_err(e)
finally:
runner.close()
if runner is not None:
runner.close()
def check_devicon_object(icon: dict):

View File

@ -42,27 +42,45 @@ jobs:
uses: devicons/public-upload-to-imgur@v2.2.2
if: success()
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}}
- 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
if: success()
uses: peter-evans/create-pull-request@v3
env:
MESSAGE: |
What's up!
Hello,
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):
![Files Built]({0})
{0}
Here is what they look like as icons:
{1}
The devicon.min.css file contains:
-The icon content
-The aliases
-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.
Adios,
@ -71,5 +89,13 @@ jobs:
branch: 'bot/build-result'
commit-message: 'Built 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

View File

@ -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

View File

@ -39,6 +39,22 @@ jobs:
with:
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
id: icons_overview_img_step
@ -53,7 +69,7 @@ jobs:
uses: devicons/public-upload-to-imgur@v2.2.2
if: env.PEEK_STATUS == 'success' && success()
with:
path: ./screenshots/screenshot_*.png
path: ./screenshots/new_icon_*.png
client_id: ${{secrets.IMGUR_CLIENT_ID}}
- name: Comment on the PR about the result - Success
@ -64,19 +80,32 @@ jobs:
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).
Here is the result below (top left):
{0}
Here are the zoomed-in screenshots of the added icons:
Here are the SVGs as intepreted by Icomoon when we upload the files:
{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.
**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,
Peek Bot :blush:
with:
@ -84,12 +113,19 @@ jobs:
issue_number: ${{ steps.pr_num_reader.outputs.content }}
token: ${{ secrets.GITHUB_TOKEN }}
body: >
${{ format(env.MESSAGE,
fromJSON(steps.icons_overview_img_step.outputs.markdown_urls)[0],
join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), '')) }}
${{
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],
join(fromJSON(steps.icons_detailed_img_step.outputs.markdown_urls), ' ')
)
}}
- 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
env:
MESSAGE: |
@ -116,3 +152,20 @@ jobs:
issue_number: ${{ steps.pr_num_reader.outputs.content }}
token: ${{ secrets.GITHUB_TOKEN }}
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

4
.gitignore vendored
View File

@ -4,4 +4,6 @@ node_modules
geckodriver.log
__pycache__
*.pyc
new_icons.png
new_icons.png
screenshots/
release_message.txt

0
devicon.css → devicon-base.css Executable file → Normal file
View File

View File

@ -1,11 +1,13 @@
const gulp = require("gulp");
const svgmin = require("gulp-svgmin");
const sass = require("gulp-sass")(require("sass"));
const footer = require("gulp-footer");
const yargs = require("yargs");
const fsPromise = require("fs").promises;
const path = require("path");
// global const
const deviconBaseCSSName = "devicon-base.css"
const deviconJSONName = "devicon.json";
const aliasSCSSName = "devicon-alias.scss";
const colorsCSSName = "devicon-colors.css";
@ -21,7 +23,7 @@ async function createDeviconMinCSS() {
await createCSSFiles();
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.
const fileContent = `@use "${aliasSCSSName}";@use "${colorsCSSName}";`;
await fsPromise.writeFile(deviconMinPath, fileContent, "utf8");
@ -59,7 +61,7 @@ async function createCSSFiles() {
*/
function createAliasSCSS(deviconJson) {
let statements = deviconJson.map(createAliasStatement).join(" ");
let sass = `@use "devicon";${statements}`;
let sass = `@use "${deviconBaseCSSName}";${statements}`;
let sassPath = path.join(__dirname, aliasSCSSName);
return fsPromise.writeFile(sassPath, sass, "utf8");
}
@ -156,6 +158,7 @@ function optimizeSvg() {
return gulp
.src(svgGlob)
.pipe(svgmin(configOptionCallback))
.pipe(footer("\n"))
.pipe(
gulp.dest(file => {
return file.base;