mirror of
https://github.com/konpa/devicon.git
synced 2025-08-04 13:47:26 +02:00
Add fix for bad zip file and logging for build (#1069)
This commit is contained in:
10
.github/scripts/build_assets/api_handler.py
vendored
10
.github/scripts/build_assets/api_handler.py
vendored
@@ -2,12 +2,13 @@ import requests
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from io import FileIO
|
||||||
|
|
||||||
|
|
||||||
# our base url which leads to devicon
|
# our base url which leads to devicon
|
||||||
base_url = "https://api.github.com/repos/devicons/devicon/"
|
base_url = "https://api.github.com/repos/devicons/devicon/"
|
||||||
|
|
||||||
def get_merged_pull_reqs_since_last_release(token):
|
def get_merged_pull_reqs_since_last_release(token, log_output: FileIO=sys.stdout):
|
||||||
"""
|
"""
|
||||||
Get all the merged pull requests since the last release.
|
Get all the merged pull requests since the last release.
|
||||||
"""
|
"""
|
||||||
@@ -16,9 +17,8 @@ def get_merged_pull_reqs_since_last_release(token):
|
|||||||
found_last_release = False
|
found_last_release = False
|
||||||
page = 1
|
page = 1
|
||||||
|
|
||||||
print("Getting PRs since last release.")
|
|
||||||
while not found_last_release:
|
while not found_last_release:
|
||||||
data = get_merged_pull_reqs(token, page)
|
data = get_merged_pull_reqs(token, page, log_output)
|
||||||
# assume we don't encounter it during the loop
|
# assume we don't encounter it during the loop
|
||||||
last_release_index = 101
|
last_release_index = 101
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ def get_merged_pull_reqs_since_last_release(token):
|
|||||||
return pull_reqs
|
return pull_reqs
|
||||||
|
|
||||||
|
|
||||||
def get_merged_pull_reqs(token, page):
|
def get_merged_pull_reqs(token, page, log_output: FileIO=sys.stdout):
|
||||||
"""
|
"""
|
||||||
Get the merged pull requests based on page. There are
|
Get the merged pull requests based on page. There are
|
||||||
100 results per page. See https://docs.github.com/en/rest/reference/pulls
|
100 results per page. See https://docs.github.com/en/rest/reference/pulls
|
||||||
@@ -53,7 +53,7 @@ def get_merged_pull_reqs(token, page):
|
|||||||
"page": page
|
"page": page
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"Querying the GitHub API for requests page #{page}")
|
print(f"Querying the GitHub API for requests page #{page}", file=log_output)
|
||||||
response = requests.get(url, headers=headers, params=params)
|
response = requests.get(url, headers=headers, params=params)
|
||||||
if not response:
|
if not response:
|
||||||
print(f"Can't query the GitHub API. Status code is {response.status_code}. Message is {response.text}")
|
print(f"Can't query the GitHub API. Status code is {response.status_code}. Message is {response.text}")
|
||||||
|
39
.github/scripts/build_assets/filehandler.py
vendored
39
.github/scripts/build_assets/filehandler.py
vendored
@@ -1,9 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile, is_zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from io import FileIO
|
||||||
|
|
||||||
|
|
||||||
def find_new_icons_in_devicon_json(devicon_json_path: str, icomoon_json_path: str):
|
def find_new_icons_in_devicon_json(devicon_json_path: str, icomoon_json_path: str):
|
||||||
@@ -133,7 +134,7 @@ def is_alias(font_version: str, aliases: List[dict]):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def extract_files(zip_path: str, extract_path: str, delete=True):
|
def extract_files(zip_path: str, extract_path: str, logfile: FileIO, delete=True):
|
||||||
"""
|
"""
|
||||||
Extract the style.css and font files from the devicon.zip
|
Extract the style.css and font files from the devicon.zip
|
||||||
folder. Must call the gulp task "get-icomoon-files"
|
folder. Must call the gulp task "get-icomoon-files"
|
||||||
@@ -144,9 +145,11 @@ def extract_files(zip_path: str, extract_path: str, delete=True):
|
|||||||
will put the extracted files.
|
will put the extracted files.
|
||||||
:param delete, whether the function should delete the zip file
|
:param delete, whether the function should delete the zip file
|
||||||
when it's done.
|
when it's done.
|
||||||
|
:param logfile
|
||||||
"""
|
"""
|
||||||
print("Extracting zipped files...")
|
print("Extracting zipped files...", file=logfile)
|
||||||
|
fixBadZipfile(zip_path, logfile)
|
||||||
|
print(f"it's zipped {is_zipfile(zip_path)}", file=logfile)
|
||||||
icomoon_zip = ZipFile(zip_path)
|
icomoon_zip = ZipFile(zip_path)
|
||||||
target_files = ('selection.json', 'fonts/', 'fonts/devicon.ttf',
|
target_files = ('selection.json', 'fonts/', 'fonts/devicon.ttf',
|
||||||
'fonts/devicon.woff', 'fonts/devicon.eot',
|
'fonts/devicon.woff', 'fonts/devicon.eot',
|
||||||
@@ -154,22 +157,40 @@ def extract_files(zip_path: str, extract_path: str, delete=True):
|
|||||||
for file in target_files:
|
for file in target_files:
|
||||||
icomoon_zip.extract(file, extract_path)
|
icomoon_zip.extract(file, extract_path)
|
||||||
|
|
||||||
print("Files extracted")
|
print("Files extracted", file=logfile)
|
||||||
|
|
||||||
if delete:
|
if delete:
|
||||||
print("Deleting devicon zip file...")
|
print("Deleting devicon zip file...", file=logfile)
|
||||||
icomoon_zip.close()
|
icomoon_zip.close()
|
||||||
os.remove(zip_path)
|
os.remove(zip_path)
|
||||||
|
|
||||||
|
|
||||||
def rename_extracted_files(extract_path: str):
|
def fixBadZipfile(zippath: str, logfile: FileIO):
|
||||||
|
"""
|
||||||
|
Fix a bad zipfile (one that causes zipfile.ZipFile to throw a BadZipfile Error).
|
||||||
|
Taken from https://stackoverflow.com/a/11385480/11683637.
|
||||||
|
"""
|
||||||
|
f = open(zippath, 'r+b')
|
||||||
|
data = f.read()
|
||||||
|
pos = data.find(b'\x50\x4b\x05\x06') # End of central directory signature
|
||||||
|
if (pos > 0):
|
||||||
|
# self._log("Trancating file at location " + str(pos + 22)+ ".")
|
||||||
|
f.seek(pos + 22) # size of 'ZIP end of central directory record'
|
||||||
|
f.truncate()
|
||||||
|
else:
|
||||||
|
print("Zipfile don't need to be fixed", file=logfile)
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def rename_extracted_files(extract_path: str, logfile: FileIO):
|
||||||
"""
|
"""
|
||||||
Rename the extracted files selection.json and style.css.
|
Rename the extracted files selection.json and style.css.
|
||||||
:param extract_path, the location where the function
|
:param extract_path, the location where the function
|
||||||
can find the extracted files.
|
can find the extracted files.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
print("Renaming files")
|
print("Renaming files", file=logfile)
|
||||||
old_to_new_list = [
|
old_to_new_list = [
|
||||||
{
|
{
|
||||||
"old": Path(extract_path, "selection.json"),
|
"old": Path(extract_path, "selection.json"),
|
||||||
@@ -184,7 +205,7 @@ def rename_extracted_files(extract_path: str):
|
|||||||
for dict_ in old_to_new_list:
|
for dict_ in old_to_new_list:
|
||||||
os.replace(dict_["old"], dict_["new"])
|
os.replace(dict_["old"], dict_["new"])
|
||||||
|
|
||||||
print("Files renamed")
|
print("Files renamed", file=logfile)
|
||||||
|
|
||||||
|
|
||||||
def create_screenshot_folder(dir, screenshot_name: str="screenshots/"):
|
def create_screenshot_folder(dir, screenshot_name: str="screenshots/"):
|
||||||
|
@@ -27,7 +27,7 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
:param icomoon_json_path: a path to the iconmoon.json.
|
:param icomoon_json_path: a path to the iconmoon.json.
|
||||||
:raises TimeoutException: happens when elements are not found.
|
:raises TimeoutException: happens when elements are not found.
|
||||||
"""
|
"""
|
||||||
print("Uploading icomoon.json file...")
|
print("Uploading icomoon.json file...", file=self.log_output)
|
||||||
|
|
||||||
# find the file input and enter the file path
|
# find the file input and enter the file path
|
||||||
import_btn = self.driver.find_element_by_css_selector(
|
import_btn = self.driver.find_element_by_css_selector(
|
||||||
@@ -44,7 +44,7 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
raise Exception("Cannot find the confirm button when uploading the icomoon.json" \
|
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")
|
"Ensure that the icomoon.json is in the correct format for Icomoon.io")
|
||||||
|
|
||||||
print("JSON file uploaded.")
|
print("JSON file uploaded.", file=self.log_output)
|
||||||
|
|
||||||
def upload_svgs(self, svgs: List[str], screenshot_folder: str):
|
def upload_svgs(self, svgs: List[str], screenshot_folder: str):
|
||||||
"""
|
"""
|
||||||
@@ -52,7 +52,7 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
:param svgs: a list of svg Paths that we'll upload to icomoon.
|
||||||
:param screenshot_folder: the name of the screenshot_folder.
|
:param screenshot_folder: the name of the screenshot_folder.
|
||||||
"""
|
"""
|
||||||
print("Uploading SVGs...")
|
print("Uploading SVGs...", file=self.log_output)
|
||||||
|
|
||||||
import_btn = self.driver.find_element_by_css_selector(
|
import_btn = self.driver.find_element_by_css_selector(
|
||||||
SeleniumRunner.SET_IMPORT_BUTTON_CSS
|
SeleniumRunner.SET_IMPORT_BUTTON_CSS
|
||||||
@@ -63,7 +63,7 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
err_messages = []
|
err_messages = []
|
||||||
for i in range(len(svgs)):
|
for i in range(len(svgs)):
|
||||||
import_btn.send_keys(svgs[i])
|
import_btn.send_keys(svgs[i])
|
||||||
print(f"Uploading {svgs[i]}")
|
print(f"Uploading {svgs[i]}", file=self.log_output)
|
||||||
|
|
||||||
# see if there are stroke messages or replacing icon message
|
# see if there are stroke messages or replacing icon message
|
||||||
# there should be none of the second kind
|
# there should be none of the second kind
|
||||||
@@ -83,8 +83,9 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
raise Exception(f"Unexpected alert found: {alert}")
|
raise Exception(f"Unexpected alert found: {alert}")
|
||||||
|
|
||||||
self.edit_svg()
|
self.edit_svg()
|
||||||
print(f"Finished editing icon.")
|
print(f"Finished editing icon.", file=self.log_output)
|
||||||
|
|
||||||
|
print("Finished uploading all files.", file=self.log_output)
|
||||||
if err_messages != []:
|
if err_messages != []:
|
||||||
message = "BuildSeleniumRunner - Issues found when uploading SVGs:\n"
|
message = "BuildSeleniumRunner - Issues found when uploading SVGs:\n"
|
||||||
raise Exception(message + '\n'.join(err_messages))
|
raise Exception(message + '\n'.join(err_messages))
|
||||||
@@ -94,9 +95,9 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
self.switch_toolbar_option(IcomoonOptionState.SELECT)
|
self.switch_toolbar_option(IcomoonOptionState.SELECT)
|
||||||
self.select_all_icons_in_top_set()
|
self.select_all_icons_in_top_set()
|
||||||
new_svgs_path = str(Path(screenshot_folder, "new_svgs.png").resolve())
|
new_svgs_path = str(Path(screenshot_folder, "new_svgs.png").resolve())
|
||||||
self.driver.save_screenshot(new_svgs_path);
|
self.driver.save_screenshot(new_svgs_path)
|
||||||
|
|
||||||
print("Finished uploading the svgs...")
|
print("Finished uploading the svgs...", file=self.log_output)
|
||||||
|
|
||||||
def take_icon_screenshot(self, screenshot_folder: str):
|
def take_icon_screenshot(self, screenshot_folder: str):
|
||||||
"""
|
"""
|
||||||
@@ -105,7 +106,7 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
:param screenshot_folder: the name of the screenshot_folder.
|
:param screenshot_folder: the name of the screenshot_folder.
|
||||||
"""
|
"""
|
||||||
# take pictures
|
# take pictures
|
||||||
print("Taking screenshot of the new icons...")
|
print("Taking screenshot of the new icons...", file=self.log_output)
|
||||||
self.go_to_generate_font_page()
|
self.go_to_generate_font_page()
|
||||||
|
|
||||||
# take an overall screenshot of the icons that were just added
|
# take an overall screenshot of the icons that were just added
|
||||||
@@ -113,8 +114,11 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
new_icons_path = str(Path(screenshot_folder, "new_icons.png").resolve())
|
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_xpath = "/html/body/div[4]/div[2]/div/div[1]"
|
||||||
main_content = self.driver.find_element_by_xpath(main_content_xpath)
|
main_content = self.driver.find_element_by_xpath(main_content_xpath)
|
||||||
|
|
||||||
|
# wait a bit for all the icons to load before we take a pic
|
||||||
|
time.sleep(SeleniumRunner.MED_WAIT_IN_SEC)
|
||||||
main_content.screenshot(new_icons_path)
|
main_content.screenshot(new_icons_path)
|
||||||
print("Saved screenshot of the new icons...")
|
print("Saved screenshot of the new icons...", file=self.log_output)
|
||||||
|
|
||||||
def go_to_generate_font_page(self):
|
def go_to_generate_font_page(self):
|
||||||
"""
|
"""
|
||||||
@@ -137,7 +141,7 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
what the icons look like.
|
what the icons look like.
|
||||||
:param zip_path: the path to the zip file after it's downloaded.
|
:param zip_path: the path to the zip file after it's downloaded.
|
||||||
"""
|
"""
|
||||||
print("Downloading Font files...")
|
print("Downloading Font files...", file=self.log_output)
|
||||||
if self.current_page != IcomoonPage.SELECTION:
|
if self.current_page != IcomoonPage.SELECTION:
|
||||||
self.go_to_page(IcomoonPage.SELECTION)
|
self.go_to_page(IcomoonPage.SELECTION)
|
||||||
|
|
||||||
@@ -149,7 +153,7 @@ class BuildSeleniumRunner(SeleniumRunner):
|
|||||||
)
|
)
|
||||||
download_btn.click()
|
download_btn.click()
|
||||||
if self.wait_for_zip(zip_path):
|
if self.wait_for_zip(zip_path):
|
||||||
print("Font files downloaded.")
|
print("Font files downloaded.", file=self.log_output)
|
||||||
else:
|
else:
|
||||||
raise TimeoutError(f"Couldn't find {zip_path} after download button was clicked.")
|
raise TimeoutError(f"Couldn't find {zip_path} after download button was clicked.")
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ class PeekSeleniumRunner(SeleniumRunner):
|
|||||||
:return an array of svgs with strokes as strings. These show which icon
|
:return an array of svgs with strokes as strings. These show which icon
|
||||||
contains stroke.
|
contains stroke.
|
||||||
"""
|
"""
|
||||||
print("Peeking SVGs...")
|
print("Peeking SVGs...", file=self.log_output)
|
||||||
|
|
||||||
import_btn = self.driver.find_element_by_css_selector(
|
import_btn = self.driver.find_element_by_css_selector(
|
||||||
SeleniumRunner.GENERAL_IMPORT_BUTTON_CSS
|
SeleniumRunner.GENERAL_IMPORT_BUTTON_CSS
|
||||||
@@ -37,13 +37,13 @@ class PeekSeleniumRunner(SeleniumRunner):
|
|||||||
svgs_with_strokes = []
|
svgs_with_strokes = []
|
||||||
for i in range(len(svgs)):
|
for i in range(len(svgs)):
|
||||||
import_btn.send_keys(svgs[i])
|
import_btn.send_keys(svgs[i])
|
||||||
print(f"Uploaded {svgs[i]}")
|
print(f"Uploaded {svgs[i]}", file=self.log_output)
|
||||||
|
|
||||||
alert = self.test_for_possible_alert(self.SHORT_WAIT_IN_SEC)
|
alert = self.test_for_possible_alert(self.SHORT_WAIT_IN_SEC)
|
||||||
if alert == None:
|
if alert == None:
|
||||||
pass # all good
|
pass # all good
|
||||||
elif alert == IcomoonAlerts.STROKES_GET_IGNORED_WARNING:
|
elif alert == IcomoonAlerts.STROKES_GET_IGNORED_WARNING:
|
||||||
print(f"- This icon contains strokes: {svgs[i]}")
|
print(f"- This icon contains strokes: {svgs[i]}", file=self.log_output)
|
||||||
svg = Path(svgs[i])
|
svg = Path(svgs[i])
|
||||||
svgs_with_strokes.append(f"- {svg.name}")
|
svgs_with_strokes.append(f"- {svg.name}")
|
||||||
self.click_alert_button(self.ALERTS[alert]["buttons"]["DISMISS"])
|
self.click_alert_button(self.ALERTS[alert]["buttons"]["DISMISS"])
|
||||||
@@ -57,9 +57,9 @@ class PeekSeleniumRunner(SeleniumRunner):
|
|||||||
new_svgs_path = str(Path(screenshot_folder, "new_svgs.png").resolve())
|
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_xpath = "/html/body/div[4]/div[1]/div[2]/div[1]"
|
||||||
icon_set = self.driver.find_element_by_xpath(icon_set_xpath)
|
icon_set = self.driver.find_element_by_xpath(icon_set_xpath)
|
||||||
icon_set.screenshot(new_svgs_path);
|
icon_set.screenshot(new_svgs_path)
|
||||||
|
|
||||||
print("Finished peeking the svgs...")
|
print("Finished peeking the svgs...", file=self.log_output)
|
||||||
return svgs_with_strokes
|
return svgs_with_strokes
|
||||||
|
|
||||||
def peek_icons(self, screenshot_folder: str, icon_info: dict):
|
def peek_icons(self, screenshot_folder: str, icon_info: dict):
|
||||||
@@ -68,7 +68,7 @@ class PeekSeleniumRunner(SeleniumRunner):
|
|||||||
:param screenshot_folder: the name of the screenshot_folder.
|
:param screenshot_folder: the name of the screenshot_folder.
|
||||||
:param icon_info: a dictionary containing info on an icon. Taken from the devicon.json.
|
:param icon_info: a dictionary containing info on an icon. Taken from the devicon.json.
|
||||||
"""
|
"""
|
||||||
print("Begin peeking at the icons...")
|
print("Begin peeking at the icons...", file=self.log_output)
|
||||||
# ensure all icons in the set is selected.
|
# ensure all icons in the set is selected.
|
||||||
self.select_all_icons_in_top_set()
|
self.select_all_icons_in_top_set()
|
||||||
self.go_to_page(IcomoonPage.GENERATE_FONT)
|
self.go_to_page(IcomoonPage.GENERATE_FONT)
|
||||||
@@ -119,4 +119,4 @@ class PeekSeleniumRunner(SeleniumRunner):
|
|||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
print("Finished peeking the icons...")
|
print("Finished peeking the icons...", file=self.log_output)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from selenium.webdriver.common import service
|
from io import FileIO
|
||||||
|
import sys
|
||||||
|
|
||||||
from selenium.webdriver.firefox.webdriver import WebDriver
|
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||||
from selenium.webdriver.firefox.service import Service
|
from selenium.webdriver.firefox.service import Service
|
||||||
@@ -8,6 +9,7 @@ from selenium.webdriver.common.by import By
|
|||||||
from selenium.webdriver.support.ui import WebDriverWait
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException
|
from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException
|
||||||
|
from selenium.common.exceptions import ElementNotInteractableException
|
||||||
|
|
||||||
from build_assets.selenium_runner.enums import IcomoonOptionState, IcomoonPage, IcomoonAlerts
|
from build_assets.selenium_runner.enums import IcomoonOptionState, IcomoonPage, IcomoonAlerts
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ class SeleniumRunner:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, download_path: str,
|
def __init__(self, download_path: str,
|
||||||
geckodriver_path: str, headless: bool):
|
geckodriver_path: str, headless: bool, log_output: FileIO=sys.stdout):
|
||||||
"""
|
"""
|
||||||
Create a SeleniumRunner object.
|
Create a SeleniumRunner object.
|
||||||
:param download_path: the location where you want to download
|
:param download_path: the location where you want to download
|
||||||
@@ -117,7 +119,16 @@ class SeleniumRunner:
|
|||||||
self.driver = None
|
self.driver = None
|
||||||
# default values when we open Icomoon
|
# default values when we open Icomoon
|
||||||
self.current_option_state = IcomoonOptionState.SELECT
|
self.current_option_state = IcomoonOptionState.SELECT
|
||||||
|
"""
|
||||||
|
Track the current option in the tool bar.
|
||||||
|
"""
|
||||||
|
|
||||||
self.current_page = IcomoonPage.SELECTION
|
self.current_page = IcomoonPage.SELECTION
|
||||||
|
"""Track the current page the driver is on."""
|
||||||
|
|
||||||
|
file=self.log_output = log_output
|
||||||
|
"""The log output stream. Default is stdout."""
|
||||||
|
|
||||||
self.set_browser_options(download_path, geckodriver_path, headless)
|
self.set_browser_options(download_path, geckodriver_path, headless)
|
||||||
|
|
||||||
def set_browser_options(self, download_path: str, geckodriver_path: str,
|
def set_browser_options(self, download_path: str, geckodriver_path: str,
|
||||||
@@ -145,7 +156,7 @@ class SeleniumRunner:
|
|||||||
options.set_preference("browser.download.dir", download_path)
|
options.set_preference("browser.download.dir", download_path)
|
||||||
options.headless = headless
|
options.headless = headless
|
||||||
|
|
||||||
print("Activating browser client...")
|
print("Activating browser client...", file=self.log_output)
|
||||||
self.driver = self.create_driver_instance(options, geckodriver_path)
|
self.driver = self.create_driver_instance(options, geckodriver_path)
|
||||||
|
|
||||||
self.driver.get(self.ICOMOON_URL)
|
self.driver.get(self.ICOMOON_URL)
|
||||||
@@ -153,7 +164,7 @@ class SeleniumRunner:
|
|||||||
WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
||||||
ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]"))
|
ec.element_to_be_clickable((By.XPATH, "(//i[@class='icon-menu'])[2]"))
|
||||||
)
|
)
|
||||||
print("Accessed icomoon.io")
|
print("Accessed icomoon.io", file=self.log_output)
|
||||||
|
|
||||||
def create_driver_instance(self, options: Options, geckodriver_path: str):
|
def create_driver_instance(self, options: Options, geckodriver_path: str):
|
||||||
"""
|
"""
|
||||||
@@ -163,44 +174,37 @@ class SeleniumRunner:
|
|||||||
:param geckodriver_path: the path to the firefox executable.
|
:param geckodriver_path: the path to the firefox executable.
|
||||||
the icomoon.zip to.
|
the icomoon.zip to.
|
||||||
"""
|
"""
|
||||||
retries = SeleniumRunner.MAX_RETRY
|
|
||||||
finished = False
|
|
||||||
driver = None
|
driver = None
|
||||||
err_msgs = [] # keep for logging purposes
|
err_msgs = [] # keep for logging purposes
|
||||||
while not finished and retries > 0:
|
for i in range(SeleniumRunner.MAX_RETRY):
|
||||||
try:
|
try:
|
||||||
# order matters, don't change the lines below
|
|
||||||
finished = True # signal we are done in case we are actually done
|
|
||||||
|
|
||||||
# customize the local server
|
# customize the local server
|
||||||
service = None
|
service = None
|
||||||
# first retry: use 8080
|
# first try: use 8080
|
||||||
# else: random
|
# else: random
|
||||||
if retries == SeleniumRunner.MAX_RETRY:
|
if i == 0:
|
||||||
service = Service(executable_path=geckodriver_path, port=8080)
|
service = Service(executable_path=geckodriver_path, port=8080)
|
||||||
else:
|
else:
|
||||||
service = Service(executable_path=geckodriver_path)
|
service = Service(executable_path=geckodriver_path)
|
||||||
driver = WebDriver(options=options, service=service)
|
driver = WebDriver(options=options, service=service)
|
||||||
except SeleniumTimeoutException as e:
|
|
||||||
# retry. This is intended to catch "no connection could be made" error
|
|
||||||
retries -= 1
|
|
||||||
finished = False # flip the var so we can retry
|
|
||||||
msg = f"Retry {retries}/{SeleniumRunner.MAX_RETRY} SeleniumTimeoutException: {e.msg}"
|
|
||||||
print(msg)
|
|
||||||
err_msgs.append(msg)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# anything else: unsure if retry works. Just end the retry
|
# retry. This is intended to catch "no connection could be made" error
|
||||||
msg = f"Retry {retries}/{SeleniumRunner.MAX_RETRY} Exception: {e}"
|
# anything else: unsure if retry works. Still retry
|
||||||
|
msg = f"Retry {i + 1}/{SeleniumRunner.MAX_RETRY} Exception: {e}"
|
||||||
err_msgs.append(msg)
|
err_msgs.append(msg)
|
||||||
print(msg)
|
print(msg, file=self.log_output)
|
||||||
|
else:
|
||||||
|
# works fine
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
# out of retries
|
||||||
|
# handle situation when we can't make a driver
|
||||||
|
err_msg_formatted = '\n'.join(reversed(err_msgs))
|
||||||
|
msg = f"Unable to create WebDriver Instance:\n{err_msg_formatted}"
|
||||||
|
raise Exception(msg)
|
||||||
|
|
||||||
if driver is not None:
|
return driver
|
||||||
return driver
|
|
||||||
|
|
||||||
err_msg_formatted = '\n'.join(reversed(err_msgs))
|
|
||||||
msg = f"Unable to create WebDriver Instance:\n{err_msg_formatted}"
|
|
||||||
raise Exception(msg)
|
|
||||||
|
|
||||||
def switch_toolbar_option(self, option: IcomoonOptionState):
|
def switch_toolbar_option(self, option: IcomoonOptionState):
|
||||||
"""
|
"""
|
||||||
@@ -311,7 +315,7 @@ class SeleniumRunner:
|
|||||||
edit_screen = self.driver.find_element_by_css_selector(
|
edit_screen = self.driver.find_element_by_css_selector(
|
||||||
edit_screen_selector)
|
edit_screen_selector)
|
||||||
edit_screen.screenshot(screenshot_path)
|
edit_screen.screenshot(screenshot_path)
|
||||||
print("Took screenshot of svg and saved it at " + screenshot_path)
|
print("Took screenshot of svg and saved it at " + screenshot_path, file=self.log_output)
|
||||||
|
|
||||||
close_btn = self.driver \
|
close_btn = self.driver \
|
||||||
.find_element_by_css_selector("div.overlayWindow i.icon-close")
|
.find_element_by_css_selector("div.overlayWindow i.icon-close")
|
||||||
@@ -334,11 +338,22 @@ class SeleniumRunner:
|
|||||||
"""
|
"""
|
||||||
Select all the svgs in the top most (latest) set.
|
Select all the svgs in the top most (latest) set.
|
||||||
"""
|
"""
|
||||||
self.click_hamburger_input()
|
tries = 3
|
||||||
select_all_button = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until(
|
for i in range(tries):
|
||||||
ec.element_to_be_clickable((By.XPATH, "//button[text()='Select All']"))
|
try:
|
||||||
)
|
self.click_hamburger_input()
|
||||||
select_all_button.click()
|
select_all_button = WebDriverWait(self.driver, self.SHORT_WAIT_IN_SEC).until(
|
||||||
|
ec.element_to_be_clickable((By.XPATH, "//button[text()='Select All']"))
|
||||||
|
)
|
||||||
|
select_all_button.click()
|
||||||
|
except ElementNotInteractableException:
|
||||||
|
# retry until it works
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# after all tries and still can't click? Raise an error
|
||||||
|
raise ElementNotInteractableException("Can't click the 'Select All' button in the hamburger input.")
|
||||||
|
|
||||||
def deselect_all_icons_in_top_set(self):
|
def deselect_all_icons_in_top_set(self):
|
||||||
"""
|
"""
|
||||||
@@ -366,5 +381,5 @@ class SeleniumRunner:
|
|||||||
"""
|
"""
|
||||||
Close the SeleniumRunner instance.
|
Close the SeleniumRunner instance.
|
||||||
"""
|
"""
|
||||||
print("Closing down SeleniumRunner...")
|
print("Closing down SeleniumRunner...", file=self.log_output)
|
||||||
self.driver.quit()
|
self.driver.quit()
|
||||||
|
8
.github/scripts/build_assets/util.py
vendored
8
.github/scripts/build_assets/util.py
vendored
@@ -4,13 +4,17 @@ from typing import List
|
|||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
from io import FileIO
|
||||||
|
|
||||||
def exit_with_err(err: Exception):
|
def exit_with_err(err: Exception, logfile: FileIO=None):
|
||||||
"""
|
"""
|
||||||
Exit the current step and display the err.
|
Exit the current step and display the err.
|
||||||
:param: err, the error/exception encountered.
|
:param: err, the error/exception encountered.
|
||||||
"""
|
"""
|
||||||
traceback.print_exc()
|
if logfile:
|
||||||
|
traceback.print_exc(file=logfile)
|
||||||
|
else:
|
||||||
|
traceback.print_exc()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
62
.github/scripts/icomoon_build.py
vendored
62
.github/scripts/icomoon_build.py
vendored
@@ -5,6 +5,7 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
from io import FileIO
|
||||||
|
|
||||||
|
|
||||||
# pycharm complains that build_assets is an unresolved ref
|
# pycharm complains that build_assets is an unresolved ref
|
||||||
@@ -18,63 +19,68 @@ def main():
|
|||||||
Build the icons using Icomoon. Also optimize the svgs.
|
Build the icons using Icomoon. Also optimize the svgs.
|
||||||
"""
|
"""
|
||||||
runner = None
|
runner = None
|
||||||
|
logfile = open("log.txt", "w")
|
||||||
try:
|
try:
|
||||||
args = arg_getters.get_selenium_runner_args()
|
args = arg_getters.get_selenium_runner_args()
|
||||||
new_icons = get_icons_for_building(args.icomoon_json_path, args.devicon_json_path, args.token)
|
new_icons = get_icons_for_building(args.icomoon_json_path, args.devicon_json_path, args.token, logfile)
|
||||||
if len(new_icons) == 0:
|
if len(new_icons) == 0:
|
||||||
sys.exit("No files need to be uploaded. Ending script...")
|
sys.exit("No files need to be uploaded. Ending script...")
|
||||||
|
|
||||||
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", file=logfile)
|
||||||
|
|
||||||
print("Begin optimizing files...")
|
print("Begin optimizing files...", file=logfile)
|
||||||
optimize_svgs(new_icons, args.icons_folder_path)
|
optimize_svgs(new_icons, args.icons_folder_path, logfile=logfile)
|
||||||
|
|
||||||
print("Updating the icomoon json...")
|
print("Updating the icomoon json...", file=logfile)
|
||||||
update_icomoon_json(new_icons, args.icomoon_json_path)
|
update_icomoon_json(new_icons, args.icomoon_json_path, logfile)
|
||||||
|
|
||||||
print("Start the building icons process...")
|
print("Start the building icons process...", file=logfile)
|
||||||
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)
|
||||||
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)
|
||||||
screenshot_folder = filehandler.create_screenshot_folder("./")
|
screenshot_folder = filehandler.create_screenshot_folder("./")
|
||||||
|
|
||||||
runner = BuildSeleniumRunner(args.download_path,
|
runner = BuildSeleniumRunner(args.download_path,
|
||||||
args.geckodriver_path, args.headless)
|
args.geckodriver_path, args.headless, log_output=logfile)
|
||||||
runner.build_icons(args.icomoon_json_path, zip_path,
|
runner.build_icons(args.icomoon_json_path, zip_path,
|
||||||
icon_svgs, screenshot_folder)
|
icon_svgs, screenshot_folder)
|
||||||
|
|
||||||
filehandler.extract_files(str(zip_path), args.download_path)
|
filehandler.extract_files(str(zip_path), args.download_path, logfile)
|
||||||
filehandler.rename_extracted_files(args.download_path)
|
filehandler.rename_extracted_files(args.download_path, logfile)
|
||||||
|
|
||||||
print("Creating the release message by querying the GitHub API...")
|
print("Creating the release message by querying the GitHub API...", file=logfile)
|
||||||
get_release_message(args.token)
|
get_release_message(args.token, logfile)
|
||||||
|
|
||||||
print("Closing issues with the `in-develop` label.")
|
print("Closing issues with the `in-develop` label.", file=logfile)
|
||||||
issues = api_handler.get_issues_by_labels(args.token, ["in-develop"])
|
issues = api_handler.get_issues_by_labels(args.token, ["in-develop"])
|
||||||
issue_nums = [issue_num["number"] for issue_num in issues]
|
issue_nums = [issue_num["number"] for issue_num in issues]
|
||||||
api_handler.close_issues(args.token, issue_nums)
|
api_handler.close_issues(args.token, issue_nums)
|
||||||
|
|
||||||
print("Task completed.")
|
print("Task completed.", file=logfile)
|
||||||
except TimeoutException as e:
|
except TimeoutException as e:
|
||||||
util.exit_with_err(Exception("Selenium Time Out Error: \n" + str(e)))
|
util.exit_with_err(Exception("Selenium Time Out Error: \n" + str(e)), logfile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
util.exit_with_err(e)
|
util.exit_with_err(e, logfile)
|
||||||
finally:
|
finally:
|
||||||
|
print("Exiting", file=logfile)
|
||||||
if runner is not None:
|
if runner is not None:
|
||||||
runner.close()
|
runner.close()
|
||||||
|
logfile.close()
|
||||||
|
|
||||||
|
|
||||||
def get_icons_for_building(icomoon_json_path: str, devicon_json_path: str, token: str):
|
def get_icons_for_building(icomoon_json_path: str, devicon_json_path: str, token: str, logfile: FileIO):
|
||||||
"""
|
"""
|
||||||
Get the icons for building.
|
Get the icons for building.
|
||||||
: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.
|
||||||
|
:param logfile.
|
||||||
:return a list of dict containing info on the icons. These are
|
:return a list of dict containing info on the icons. These are
|
||||||
from the `devicon.json`.
|
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, logfile)
|
||||||
new_icons = []
|
new_icons = []
|
||||||
|
|
||||||
for pull_req in pull_reqs:
|
for pull_req in pull_reqs:
|
||||||
@@ -95,22 +101,24 @@ def get_icons_for_building(icomoon_json_path: str, devicon_json_path: str, token
|
|||||||
return new_icons
|
return new_icons
|
||||||
|
|
||||||
|
|
||||||
def optimize_svgs(new_icons: List[dict], icons_folder_path: str):
|
def optimize_svgs(new_icons: List[str], icons_folder_path: str, logfile: FileIO):
|
||||||
"""
|
"""
|
||||||
Optimize the newly added svgs. This is done in batches
|
Optimize the newly added svgs. This is done in batches
|
||||||
since the command line has a limit on characters allowed.
|
since the command line has a limit on characters allowed.
|
||||||
:param new_icons - the new icons that need to be optimized.
|
:param new_icons - the new icons that need to be optimized.
|
||||||
:param icons_folder_path - the path to the /icons folder.
|
:param icons_folder_path - the path to the /icons folder.
|
||||||
|
:param logfile - the file obj to store logging info in.
|
||||||
"""
|
"""
|
||||||
svgs = filehandler.get_svgs_paths(new_icons, icons_folder_path, icon_versions_only=False)
|
svgs = filehandler.get_svgs_paths(new_icons, icons_folder_path, icon_versions_only=False)
|
||||||
start = 0
|
start = 0
|
||||||
step = 10
|
step = 10
|
||||||
for i in range(start, len(svgs), step):
|
for i in range(start, len(svgs), step):
|
||||||
batch = svgs[i:i + step]
|
batch = svgs[i:i + step]
|
||||||
|
print(f"Optimizing these files\n{batch}", file=logfile)
|
||||||
subprocess.run(["npm", "run", "optimize-svg", "--", f"--svgFiles={json.dumps(batch)}"], shell=True)
|
subprocess.run(["npm", "run", "optimize-svg", "--", f"--svgFiles={json.dumps(batch)}"], shell=True)
|
||||||
|
|
||||||
|
|
||||||
def update_icomoon_json(new_icons: List[str], icomoon_json_path: str):
|
def update_icomoon_json(new_icons: List[str], icomoon_json_path: str, logfile: FileIO):
|
||||||
"""
|
"""
|
||||||
Update the `icomoon.json` if it contains any icons
|
Update the `icomoon.json` if it contains any icons
|
||||||
that needed to be updated. This will remove the icons
|
that needed to be updated. This will remove the icons
|
||||||
@@ -127,7 +135,7 @@ def update_icomoon_json(new_icons: List[str], icomoon_json_path: str):
|
|||||||
icomoon_json["icons"] = list(icons_to_keep)
|
icomoon_json["icons"] = list(icons_to_keep)
|
||||||
|
|
||||||
new_len = len(icomoon_json["icons"])
|
new_len = len(icomoon_json["icons"])
|
||||||
print(f"Update completed. Removed {cur_len - new_len} icons:", *messages, sep='\n')
|
print(f"Update completed. Removed {cur_len - new_len} icons:", *messages, sep='\n', file=logfile)
|
||||||
filehandler.write_to_file(icomoon_json_path, json.dumps(icomoon_json))
|
filehandler.write_to_file(icomoon_json_path, json.dumps(icomoon_json))
|
||||||
|
|
||||||
|
|
||||||
@@ -149,18 +157,18 @@ def find_icomoon_icon_not_in_new_icons(icomoon_icon: Dict, new_icons: List, mess
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_release_message(token):
|
def get_release_message(token, logfile: FileIO):
|
||||||
"""
|
"""
|
||||||
Get the release message for the latest build and write
|
Get the release message for the latest build and write
|
||||||
the result in a file.
|
the result in a file.
|
||||||
:param token: the GitHub API token to access the API.
|
:param token: the GitHub API token to access the API.
|
||||||
"""
|
"""
|
||||||
# fetch first page by default
|
# fetch first page by default
|
||||||
data = api_handler.get_merged_pull_reqs_since_last_release(token)
|
data = api_handler.get_merged_pull_reqs_since_last_release(token, logfile)
|
||||||
newIcons = []
|
newIcons = []
|
||||||
features = []
|
features = []
|
||||||
|
|
||||||
print("Parsing through the pull requests...")
|
print("Parsing through the pull requests...", file=logfile)
|
||||||
for pullData in data:
|
for pullData in data:
|
||||||
authors = api_handler.find_all_authors(pullData, token)
|
authors = api_handler.find_all_authors(pullData, token)
|
||||||
markdown = f"- [{pullData['title']}]({pullData['html_url']}) by {authors}."
|
markdown = f"- [{pullData['title']}]({pullData['html_url']}) by {authors}."
|
||||||
@@ -170,7 +178,7 @@ def get_release_message(token):
|
|||||||
else:
|
else:
|
||||||
features.append(markdown)
|
features.append(markdown)
|
||||||
|
|
||||||
print("Constructing message...")
|
print("Constructing message...", file=logfile)
|
||||||
thankYou = "A huge thanks to all our maintainers and contributors for making this release possible!"
|
thankYou = "A huge thanks to all our maintainers and contributors for making this release possible!"
|
||||||
iconTitle = f"**{len(newIcons)} New Icons**"
|
iconTitle = f"**{len(newIcons)} New Icons**"
|
||||||
featureTitle = f"**{len(features)} New Features**"
|
featureTitle = f"**{len(features)} New Features**"
|
||||||
@@ -178,10 +186,10 @@ def get_release_message(token):
|
|||||||
iconTitle, "\n".join(newIcons),
|
iconTitle, "\n".join(newIcons),
|
||||||
featureTitle, "\n".join(features))
|
featureTitle, "\n".join(features))
|
||||||
|
|
||||||
print("--------------Here is the build message--------------\n", finalString)
|
print("--------------Here is the build message--------------\n", finalString, file=logfile)
|
||||||
release_message_path = "./release_message.txt"
|
release_message_path = "./release_message.txt"
|
||||||
filehandler.write_to_file(release_message_path, finalString)
|
filehandler.write_to_file(release_message_path, finalString)
|
||||||
print("Script finished")
|
print("Script finished", file=logfile)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
7
.github/workflows/build_icons.yml
vendored
7
.github/workflows/build_icons.yml
vendored
@@ -33,6 +33,13 @@ jobs:
|
|||||||
name: geckodriver-log
|
name: geckodriver-log
|
||||||
path: ./geckodriver.log
|
path: ./geckodriver.log
|
||||||
|
|
||||||
|
- name: Upload log file for debugging purposes
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: logfile
|
||||||
|
path: ./log.txt
|
||||||
|
|
||||||
- name: Build devicon.min.css
|
- name: Build devicon.min.css
|
||||||
if: success()
|
if: success()
|
||||||
run: npm run build-css
|
run: npm run build-css
|
||||||
|
@@ -155,7 +155,6 @@ function cleanUp() {
|
|||||||
*/
|
*/
|
||||||
function optimizeSvg() {
|
function optimizeSvg() {
|
||||||
let svgGlob = JSON.parse(yargs.argv.svgFiles);
|
let svgGlob = JSON.parse(yargs.argv.svgFiles);
|
||||||
console.log("Optimizing these files: ", svgGlob);
|
|
||||||
return gulp
|
return gulp
|
||||||
.src(svgGlob)
|
.src(svgGlob)
|
||||||
.pipe(svgmin(configOptionCallback))
|
.pipe(svgmin(configOptionCallback))
|
||||||
|
Reference in New Issue
Block a user