1
0
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:
Thomas Bui
2022-03-20 10:04:09 -07:00
committed by GitHub
parent ac68b12d20
commit 39dfa5416e
9 changed files with 154 additions and 96 deletions

View File

@@ -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}")

View File

@@ -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/"):

View File

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

View File

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

View File

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

View File

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

View File

@@ -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__":

View File

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

View File

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