Skip to content

Commit

Permalink
Tools: Run black, isort, ruff, pyright
Browse files Browse the repository at this point in the history
  • Loading branch information
cal0pteryx committed Jun 26, 2023
1 parent b72b843 commit 10291a9
Show file tree
Hide file tree
Showing 8 changed files with 472 additions and 498 deletions.
73 changes: 35 additions & 38 deletions tools/lint_software_list.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
#!/usr/bin/env python3
'''
Lint {clients,servers,libraries}.json
'''
"""
Lint software.json
"""
from typing import Any

import json
import os.path
import sys

VALID_ENTRY_KEYS = {
'platforms',
'name',
'doap',
'url',
'categories',
"platforms",
"name",
"doap",
"url",
"categories",
}


def emit_violation(entry_name: str,
message: str,
warning: bool = False
) -> None:
'''Prints warnings and errors'''
prefix = 'WARN' if warning else 'ERROR'
print(f'{prefix}: entry {entry_name!r}: {message}', file=sys.stderr)
def emit_violation(entry_name: str, message: str, warning: bool = False) -> None:
"""Prints warnings and errors"""
prefix = "WARN" if warning else "ERROR"
print(f"{prefix}: entry {entry_name!r}: {message}", file=sys.stderr)


def check_entries(entries: dict[str, Any]) -> int:
'''Checks entries for violations and returns their count'''
def check_entries(entries: list[dict[str, Any]]) -> int:
"""Checks entries for violations and returns their count"""
violations = 0
previous_name = None
previous_key = None
for entry in entries:
key = entry['name'].casefold()
key = entry["name"].casefold()
if previous_key is not None and previous_key > key:
emit_violation(
entry['name'],
f'should be placed in front of {previous_name!r} (all entries must be '
f'ordered alphabetically by case-folded name)'
entry["name"],
f"should be placed in front of {previous_name!r} (all entries must be "
f"ordered alphabetically by case-folded name)",
)
violations += 1

Expand All @@ -47,46 +44,46 @@ def check_entries(entries: dict[str, Any]) -> int:

if unknown:
emit_violation(
entry['name'],
f'has unknown keys: {", ".join(map(repr, unknown))}'
entry["name"], f'has unknown keys: {", ".join(map(repr, unknown))}'
)
violations += 1

if missing:
emit_violation(
entry['name'],
f'misses the following required properties: '
entry["name"],
f"misses the following required properties: "
f'{", ".join(map(repr, missing))} '
f'(see other entries for a reference)'
f"(see other entries for a reference)",
)
violations += 1

supported_platforms = entry.get('platforms', [])
supported_platforms = entry.get("platforms", [])

sorted_platforms = sorted(supported_platforms,
key=lambda x: x.casefold())
sorted_platforms = sorted(supported_platforms, key=lambda x: x.casefold())
if sorted_platforms != supported_platforms:
emit_violation(
entry['name'],
f'platform order must be: '
entry["name"],
f"platform order must be: "
f'{", ".join(map(repr, sorted_platforms))} '
f'(platforms must be ordered alphabetically)'
f"(platforms must be ordered alphabetically)",
)
violations += 1

previous_key, previous_name = key, entry['name']
previous_key, previous_name = key, entry["name"]

return violations


if __name__ == '__main__':
if __name__ == "__main__":
base_path = os.path.dirname(os.path.abspath(sys.argv[0]))
input_file = os.path.join(base_path, '../data/software.json')
with open(input_file, 'rb') as data_file:
input_file = os.path.join(base_path, "../data/software.json")
with open(input_file, "rb") as data_file:
data = json.load(data_file)

violations_count = check_entries(data)
if violations_count:
print(f'Found {violations_count} severe violations. Please fix them.',
file=sys.stderr)
print(
f"Found {violations_count} severe violations. Please fix them.",
file=sys.stderr,
)
sys.exit(1)
59 changes: 32 additions & 27 deletions tools/newsletter_email.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,75 @@
#!/usr/bin/env python3

'''In order to have newsletter e-mails formatted properly,
"""In order to have newsletter e-mails formatted properly,
we need to add inline style attributes to some elements.
This tool takes a post URL for input, processes its HTML,
and stores it afterwards ready for copy and paste.'''
and stores it afterwards ready for copy and paste."""

from http import HTTPStatus

import requests
from bs4 import BeautifulSoup
from bs4 import Tag


def process(input_url: str) -> None:
'''Processes page content for sending via e-mail.'''
"""Processes page content for sending via e-mail."""

print('Processing...')
print("Processing...")
with requests.get(input_url, timeout=5) as response:
if response.status_code != HTTPStatus.OK:
print('Could not fetch URL:', input_url)
print("Could not fetch URL:", input_url)
return
soup = BeautifulSoup(response.text, 'html.parser')
soup = BeautifulSoup(response.text, "html.parser")

article = soup.find('article', {'role': 'main'})
article = soup.find("article", {"role": "main"})
if article is None:
print('Could not find post’s article element.')
print("Could not find post’s article element.")
return

assert isinstance(article, Tag)
# Remove social share box, since it uses FontAwesome icons
# (not available in emails)
social_share = article.find('section', {'id': 'social-share'})
if social_share is not None:
social_share = article.find("section", {"id": "social-share"})
if isinstance(social_share, Tag):
social_share.decompose()

# Add body padding
article['style'] = 'padding: 2em;'
article["style"] = "padding: 2em;"

# Change color and text decoration of heading
header_box = article.find('div', {'class': 'header-internal'})
link = header_box.find('a')
link['style'] = 'text-decoration: none; color: #333;'
header_box = article.find("div", {"class": "header-internal"})
assert isinstance(header_box, Tag)
link = header_box.find("a")
assert isinstance(link, Tag)
link["style"] = "text-decoration: none; color: #333;"

# Change post meta color
meta_span = header_box.find('span', {'class': 'text-body-secondary'})
meta_span['style'] = 'color: gray;'
meta_span = header_box.find("span", {"class": "text-body-secondary"})
assert isinstance(meta_span, Tag)
meta_span["style"] = "color: gray;"

# Improve rendering of figures
figures = article.find_all('figure')
figures = article.find_all("figure")
for figure in figures:
img = figure.find('img')
img['style'] = 'max-width: 100%;'
img = figure.find("img")
img["style"] = "max-width: 100%;"

with open('newsletter-mail.html', 'w', encoding='utf-8') as html_file:
with open("newsletter-mail.html", "w", encoding="utf-8") as html_file:
html_file.write(str(article))
print(
'All done! Please copy and paste contents from "newsletter-mail.html" '
'into your e-mail client of choice (use "Insert HTML").'
)


if __name__ == '__main__':
print(50 * '=')
if __name__ == "__main__":
print(50 * "=")
print(
'This tool processes newsletter posts for emails.\n'
'It takes a post URL, processes its content, '
'and saves HTML ready for copy and paste.'
"This tool processes newsletter posts for emails.\n"
"It takes a post URL, processes its content, "
"and saves HTML ready for copy and paste."
)
print(50 * '=')
url = input('Please paste the URL you want to process: ')
print(50 * "=")
url = input("Please paste the URL you want to process: ")
process(url)
104 changes: 55 additions & 49 deletions tools/prepare_compliance.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'''
"""
Adds compliance ratings to software_list_doap.json via
compliancer (https://code.zash.se/compliancer/)
'''
"""

import json
import os
Expand All @@ -12,93 +12,99 @@
from colorama import Fore
from colorama import Style
from packaging.version import Version as V

from util import download_file

DOWNLOAD_PATH = Path('downloads')
DATA_PATH = Path('data')
COMPLIANCE_SUITE_URL = 'https://xmpp.org/extensions/xep-0459.xml'
COMPLIANCER_BUILD_URL = 'https://prosody.im/files/compliance'
DOWNLOAD_PATH = Path("downloads")
DATA_PATH = Path("data")
COMPLIANCE_SUITE_URL = "https://xmpp.org/extensions/xep-0459.xml"
COMPLIANCER_BUILD_URL = "https://prosody.im/files/compliance"


def generate_compliance_json() -> None:
'''
"""
Runs the 'compliancer' tool to generate a 'comppliance_suite.json' file
'''
"""
try:
result = subprocess.check_output([
'lua',
f'{DOWNLOAD_PATH}/compliancer',
'-v',
f'{DOWNLOAD_PATH}/compliance-suite.xml'])
result = subprocess.check_output(
[
"lua",
f"{DOWNLOAD_PATH}/compliancer",
"-v",
f"{DOWNLOAD_PATH}/compliance-suite.xml",
]
)
json_result = json.loads(result)
with open(DATA_PATH / 'compliance_suite.json',
'w',
encoding='utf-8') as compliance_suite_file:
with open(
DATA_PATH / "compliance_suite.json", "w", encoding="utf-8"
) as compliance_suite_file:
json.dump(json_result, compliance_suite_file, indent=4)
except subprocess.CalledProcessError as err:
print(err)


def check_packages_compliance() -> None:
'''
"""
Runs the 'compliancer' tool against every package's DOAP file and adds the
result to '{clients,libraries,servers}_list.doap'
'''
"""

def add_compliance_data() -> None:
with open(DATA_PATH / 'software_list_doap.json',
'rb') as json_file:
with open(DATA_PATH / "software_list_doap.json", "rb") as json_file:
package_list = json.load(json_file)

for name, props in package_list.items():
compliance_data = compliance_dict.pop(name, None)
if compliance_data is None:
props['badges'] = {}
props["badges"] = {}
continue

props['badges'] = compliance_data['badges']
props["badges"] = compliance_data["badges"]

with open(DATA_PATH / 'software_list_doap.json',
'w',
encoding='utf-8') as clients_data_file:
with open(
DATA_PATH / "software_list_doap.json", "w", encoding="utf-8"
) as clients_data_file:
json.dump(package_list, clients_data_file, indent=4)

compliance_dict: dict[
str, dict[str, str | dict[str, list[str]]]] = {}
compliance_dict: dict[str, dict[str, str | dict[str, list[str]]]] = {}

for subdir, _dirs, files in os.walk(f'{DOWNLOAD_PATH}/doap_files'):
for subdir, _dirs, files in os.walk(f"{DOWNLOAD_PATH}/doap_files"):
for file in files:
try:
result = subprocess.check_output([
'lua',
f'{DOWNLOAD_PATH}/compliancer',
'-v',
f'{DOWNLOAD_PATH}/compliance-suite.xml',
os.path.join(subdir, file)])
json_result = json.loads(result.decode('unicode_escape'))
compliance_dict[json_result['name']] = json_result
result = subprocess.check_output(
[
"lua",
f"{DOWNLOAD_PATH}/compliancer",
"-v",
f"{DOWNLOAD_PATH}/compliance-suite.xml",
os.path.join(subdir, file),
]
)
json_result = json.loads(result.decode("unicode_escape"))
compliance_dict[json_result["name"]] = json_result
except subprocess.CalledProcessError as err:
print(err)

add_compliance_data()

for _name, props in compliance_dict.items():
if props['badges']:
print(f'{Fore.YELLOW}Compliance data available, but no match for'
f'{Style.RESET_ALL}:',
props['name'])
if props["badges"]:
print(
f"{Fore.YELLOW}Compliance data available, but no match for"
f"{Style.RESET_ALL}:",
props["name"],
)


if __name__ == '__main__':
if __name__ == "__main__":
# Make sure we're using Lua >= 5.2
lua_version_string = subprocess.check_output(
['lua', '-v']).decode('unicode_escape')[4:9]
if V(lua_version_string) < V('5.2.0'):
print('Lua >= 5.2.0 required')
lua_version_string = subprocess.check_output(["lua", "-v"]).decode(
"unicode_escape"
)[4:9]
if V(lua_version_string) < V("5.2.0"):
print("Lua >= 5.2.0 required")
sys.exit(1)

download_file(
COMPLIANCE_SUITE_URL, Path('compliance-suite.xml'))
download_file(COMPLIANCER_BUILD_URL, Path('compliancer'))
download_file(COMPLIANCE_SUITE_URL, Path("compliance-suite.xml"))
download_file(COMPLIANCER_BUILD_URL, Path("compliancer"))
generate_compliance_json()
check_packages_compliance()
Loading

0 comments on commit 10291a9

Please sign in to comment.