diff --git a/src/json_parser.py b/src/json_parser.py deleted file mode 100644 index f0f9c8b..0000000 --- a/src/json_parser.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/main.py b/src/main.py index f4b05be..679cb63 100644 --- a/src/main.py +++ b/src/main.py @@ -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__": diff --git a/src/models/savefile.py b/src/models/savefile.py new file mode 100644 index 0000000..17b840f --- /dev/null +++ b/src/models/savefile.py @@ -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) \ No newline at end of file diff --git a/src/psm_parser.py b/src/psm_parser.py index 90507d8..4a34409 100644 --- a/src/psm_parser.py +++ b/src/psm_parser.py @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/xml_parser.py b/src/xml_parser.py index eb9123b..088b260 100644 --- a/src/xml_parser.py +++ b/src/xml_parser.py @@ -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: """