Refactor codebase to use a savefile and fetch data online

This commit is contained in:
Esa Kataja
2025-04-07 16:16:15 +03:00
parent f5b26d75bc
commit 5f7f496620
5 changed files with 157 additions and 81 deletions
-42
View File
@@ -1,42 +0,0 @@
import json
from pathlib import Path
import click
def _get_fields(json_data: dict) -> list[str]:
"""
Extract field names from a JSON data structure.
Args:
json_data (dict): The JSON data from which to extract field names.
Returns:
list[str]: A list of field names extracted from the JSON data.
"""
namelist = []
for attr in json_data["conceptCodes"]:
for name in attr["attributes"]:
if name["attributeName"] not in namelist:
namelist.append(name["attributeName"])
return namelist
def parse_json(json_file: Path) -> list[str]:
"""
Parse a JSON file and extract field names.
Args:
json_file (Path): The path to the JSON file to parse.
Returns:
list[str]: A list of field names extracted from the JSON file.
Raises:
click.FileError: If the file is not a valid JSON file.
"""
try:
with open(json_file, "r") as f:
json_data = json.load(f)
except json.JSONDecodeError:
raise click.FileError(json_file, "Not a valid JSON file.")
return _get_fields(json_data)
+113 -14
View File
@@ -1,23 +1,122 @@
import click
from pathlib import Path
import json
from datetime import datetime
from pydantic import HttpUrl
import httpx
from urllib.parse import urlparse, urlencode
from json_parser import parse_json
from models.savefile import SaveFile, CodeItem
from psm_parser import write_psm
from xml_parser import get_xml_data, add_connection, write_xml_data
from xml_parser import write_xml_data
def _strip_params(url: HttpUrl) -> str:
parsed_url = urlparse(str(url))
return f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
@click.command
@click.option('-j', '--json', type=Path, required=True, help='Path to the JSON file')
@click.option('-x', '--xml', type=Path, required=True, help='Path to the XML file. If the file does not exist, a default configuration will be generated.')
@click.option('-n', '--name', type=str, required=True, help='Name of the connection')
@click.option('-d', '--description', type=str, required=True, help='Description of the connection')
@click.option('-p', '--pretty', is_flag=True, default=False, help='Enable pretty output')
def main(json: Path, xml: Path, name: str, description: str, pretty: bool):
xml_data = get_xml_data(xml)
field_names = parse_json(json)
root = add_connection(xml_data, name, field_names, description=description)
write_xml_data(root, xml, pretty=pretty)
write_psm(name, field_names)
def get_attributes(url: str) -> list[str]:
"""
Get attributes from a URL.
Args:
url (str): The URL to get attributes from.
Returns:
list[str]: A list of attributes.
"""
query_params = {"pageSize": 500}
new_url = f"{url}?{urlencode(query_params, doseq=True)}"
response = httpx.get(new_url)
if response.status_code != 200:
click.echo(f"Failed to get attributes from {url}")
return []
conseptCodes = response.json()["conceptCodes"]
retval = []
for attr in conseptCodes:
for name in attr["attributes"]:
if name["attributeName"] not in retval:
retval.append(name["attributeName"])
return retval
def write_json(json_file: Path, data: SaveFile):
with open(json_file, "w") as f:
f.write(data.model_dump_json())
@click.group
def main():
click.echo("Varha CodeServer Connector")
pass
@main.command
@click.argument('json_file', type=Path, required=True)
@click.option('--url', '-u', type=str, required=True, help="URL of the connection")
@click.option('--name', '-n', type=str, required=True, help="Name of the connection")
def add(json_file: Path, url: str, name: str):
"""
Add a new connection to the JSON file.
Args:
json_file (Path): The path to the JSON file.
url (str): The URL of the connection.
name (str): The name of the connection.
"""
server_data = SaveFile()
if json_file.exists():
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
# Check if name already exists
existing_names = [code.name for code in server_data.conceptCodes]
if name in existing_names:
click.echo(f"Name {name} already exists.")
return
# Get attributes from the URL
attributes = get_attributes(_strip_params(url))
server_data.conceptCodes.append(CodeItem(name=name, url=_strip_params(url), attributes=attributes))
server_data.updated_at = datetime.now()
write_json(json_file, server_data)
@main.command
@click.argument('json_file', type=Path, required=True)
def listUrls(json_file: Path):
#TODO: Format the output
if not json_file.exists():
click.echo(f"File {json_file} does not exist.")
return
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
click.echo(server_data.model_dump_json(indent=4))
@main.command
@click.argument('json_file', type=Path, required=True)
@click.option('--name', '-n', type=str, required=True, help="Name of the connection")
def remove(json_file: Path, name: str):
if not json_file.exists():
click.echo(f"File {json_file} does not exist.")
return
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
server_data.conceptCodes = [code for code in server_data.conceptCodes if code.name != name]
server_data.updated_at = datetime.now()
write_json(json_file, server_data)
@main.command
@click.argument('json_file', type=Path, required=True)
def write_output(json_file: Path):
if not json_file.exists():
click.echo(f"File {json_file} does not exist.")
return
server_data = None
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
write_psm(server_data)
write_xml_data(server_data)
if __name__ == "__main__":
+14
View File
@@ -0,0 +1,14 @@
from pydantic import BaseModel, Field
from datetime import datetime
class CodeItem(BaseModel):
name: str
url: str
attributes: list[str]
checked_at: datetime = Field(default_factory=datetime.now)
class SaveFile(BaseModel):
version: int = 1
conceptCodes: list[CodeItem] = Field(default_factory=list)
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
+16 -15
View File
@@ -1,8 +1,7 @@
import shutil
from pathlib import Path
from datetime import datetime
from xml_parser import sanitize_name
from models.savefile import SaveFile
get_all_code = """
function Get-AllCodeServerPages!NAME! {
@@ -145,17 +144,19 @@ def define_strings(items: list[str]) -> str:
return "\n".join(["\t" + "[string]$" + "{" + sanitize_name(item) + "}" for item in items])
def write_psm(name, items: list[str], filename: Path = Path("CodeServerConnector.psm1")) -> str:
if filename.exists():
backup_filename = str(filename) + f".{datetime.now().strftime('%Y-%m-%d.%H%M%S')}.bak"
shutil.copy2(filename, backup_filename)
def write_psm(savefile: SaveFile) -> None:
output = "# ------- BEGIN CODE BLOCK -------\n"
output += get_all_code.replace("!NAME!", name)
output += get_code.replace("!NAME!", name)
output += convert_from.replace("!NAME!", name).replace("!MAPS!", define_maps(items))
output += class_definition.replace("!NAME!", name).replace("!VARIABLES!", define_variables(items)).replace("!STRINGS!", define_strings(items))
output += "# ------- END CODE BLOCK -------\n"
with open(filename, "a", encoding="utf-8-sig") as f:
f.write(output)
pass
filename = Path("output/CodeServerConnector.psm1")
if not filename.parent.exists():
filename.parent.mkdir(parents=True, exist_ok=True)
with open(filename, "w", encoding="utf-8-sig") as f:
f.write("# ------- BEGIN CODE BLOCK -------\n")
for code in savefile.conceptCodes:
f.write(get_all_code.replace("!NAME!", code.name))
f.write(get_code.replace("!NAME!", code.name))
f.write(convert_from.replace("!NAME!", code.name).replace("!MAPS!", define_maps(code.attributes)))
f.write(class_definition.replace("!NAME!", code.name).replace("!VARIABLES!", define_variables(code.attributes)).replace("!STRINGS!", define_strings(code.attributes)))
f.write("# ------- END CODE BLOCK -------\n")
return
+14 -10
View File
@@ -3,16 +3,20 @@ from pathlib import Path
import click
from datetime import datetime
def write_xml_data(root: ET.Element, file_path: Path, pretty: bool = True) -> None:
if file_path.exists():
file_path.rename(str(file_path) + f".{datetime.now().strftime('%Y-%m-%d.%H%M%S')}.bak")
xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=pretty)
if pretty:
# Parse the XML string to ensure proper indentation
parser = ET.XMLParser(remove_blank_text=True, strip_cdata=False)
root = ET.fromstring(xml_str, parser=parser)
xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=pretty)
file_path.write_bytes(xml_str)
from models.savefile import SaveFile
def write_xml_data(savefile: SaveFile) -> None:
filename = Path("output/CodeServerConnector.xml")
if not filename.parent.exists():
filename.parent.mkdir(parents=True, exist_ok=True)
root = _generate_xml_configuration()
for code in savefile.conceptCodes:
root = add_connection(root, code.name, code.attributes)
xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=True).decode("utf-8-sig")
with open(filename, "w", encoding="utf-8-sig") as f:
f.write(xml_str)
def sanitize_name(name: str) -> str:
"""