tools/azure-rest-api-specs-examples-automation/dotnet/main.py (241 lines of code) (raw):

import sys import os from os import path import glob import json import argparse import logging import dataclasses from typing import List import importlib.util from models import DotNetExample from build import DotNetBuild spec_location = ( "./directory/examples_dir.py" if path.exists("./directory/examples_dir.py") else "../directory/examples_dir.py" ) spec = importlib.util.spec_from_file_location("examples_dir", spec_location) examples_dir = importlib.util.module_from_spec(spec) spec.loader.exec_module(examples_dir) script_path: str = "." tmp_path: str specs_path: str sdk_package_path: str original_file_key: str = "// Generated from example definition: " module_relative_path: str = "" @dataclasses.dataclass(eq=True, frozen=True) class Release: tag: str package: str version: str @dataclasses.dataclass(eq=True) class DotNetExampleMethodContent: example_relative_path: str = None content: List[str] = None line_start: int = None line_end: int = None def is_valid(self) -> bool: return self.example_relative_path is not None @dataclasses.dataclass(eq=True) class AggregatedDotNetExample: methods: List[DotNetExampleMethodContent] class_opening: List[str] = None def is_aggregated_dotnet_example(lines: List[str]) -> bool: # check metadata to see if the sample file is a candidate for example extraction for line in lines: if line.strip().startswith(original_file_key): return True return False def get_dotnet_example_method(lines: List[str], start: int) -> DotNetExampleMethodContent: # extract one example method, start from certain line number method_indent = None dotnet_example_method = DotNetExampleMethodContent() for index in range(len(lines)): if index < start: continue line = lines[index] if line.strip().startswith(original_file_key): original_file = line.strip()[len(original_file_key) :] # begin of method dotnet_example_method.example_relative_path = original_file dotnet_example_method.line_start = index method_indent = len(line) - len(line.lstrip()) elif method_indent and line.rstrip() == (" " * (method_indent - 4) + "}"): # end of method dotnet_example_method.line_end = index break dotnet_example_method.content = lines[dotnet_example_method.line_start : dotnet_example_method.line_end] return dotnet_example_method def get_dotnet_using_statements(lines: List[str]) -> List[str]: lines_using_statements = [ # these are some using statements that every sample program should use. "using Azure;\n", "using Azure.ResourceManager;\n", ] for line in lines: if line.startswith("using NUnit."): # ignore the NUnit namespaces if any pass elif line.startswith("using "): lines_using_statements.append(line) elif line.startswith("namespace "): # remove the prefix first namespace = line[len("namespace ") :].strip() # remove the '.Samples' suffix if any if namespace.endswith(".Samples"): namespace = namespace[: -len(".Samples")] lines_using_statements.append(f"using {namespace};\n") break return deduplicate_list(lines_using_statements) def deduplicate_list(list: List[str]) -> List[str]: seen = set() result: List[str] = [] for item in list: if item not in seen: seen.add(item) result.append(item) return result def break_down_aggregated_dotnet_example(lines: List[str]) -> AggregatedDotNetExample: aggregated_dotnet_example = AggregatedDotNetExample([]) aggregated_dotnet_example.class_opening = get_dotnet_using_statements(lines) aggregated_dotnet_example.class_opening.append("\n") dotnet_example_method = get_dotnet_example_method(lines, 0) while dotnet_example_method.is_valid(): aggregated_dotnet_example.methods.append(dotnet_example_method) dotnet_example_method = get_dotnet_example_method(lines, dotnet_example_method.line_end) return aggregated_dotnet_example def format_dotnet(lines: List[str]) -> List[str]: # format example as DotNet code base_indent = len(lines[0]) - len(lines[0].lstrip()) last_good_indent = 0 new_lines = [] for line in lines: indent = len(line) - len(line.lstrip()) if indent >= base_indent: line = line[base_indent:] last_good_indent = indent - base_indent else: if line.strip(): line = " " * last_good_indent + line new_lines.append(line) return new_lines def process_dotnet_example(filepath: str) -> List[DotNetExample]: # process aggregated DotNet sample to examples filename = path.basename(filepath) logging.info(f"Processing DotNet aggregated sample: {filename}") with open(filepath, encoding="utf-8") as f: lines = f.readlines() dotnet_examples = [] if is_aggregated_dotnet_example(lines): aggregated_dotnet_example = break_down_aggregated_dotnet_example(lines) for dotnet_example_method in aggregated_dotnet_example.methods: if dotnet_example_method.is_valid(): logging.info(f"Processing DotNet example: {dotnet_example_method.example_relative_path}") # re-construct the example class, from example method example_lines = aggregated_dotnet_example.class_opening + format_dotnet(dotnet_example_method.content) example_filepath = dotnet_example_method.example_relative_path example_dir, example_filename = path.split(example_filepath) try: example_dir = examples_dir.try_find_resource_manager_example( specs_path, sdk_package_path, example_dir, example_filename ) except NameError: pass filename = example_filename.split(".")[0] # use the examples-dotnet folder for DotNet example md_dir = ( (example_dir + "-dotnet") if example_dir.endswith("/examples") else example_dir.replace("/examples/", "/examples-dotnet/") ) dotnet_example = DotNetExample(filename, md_dir, "".join(example_lines)) dotnet_examples.append(dotnet_example) return dotnet_examples def generate_examples(release: Release, sdk_examples_path: str, dotnet_examples: List[DotNetExample]) -> List[str]: # generate code and metadata from DotNet examples global module_relative_path files = [] for dotnet_example in dotnet_examples: doc_link = f"https://github.com/Azure/azure-sdk-for-net/blob/{release.tag}/{module_relative_path}/README.md" files.extend( write_code_to_file( sdk_examples_path, dotnet_example.target_dir, dotnet_example.target_filename, ".cs", dotnet_example.content, doc_link, ) ) return files def write_code_to_file( sdk_examples_path: str, target_dir: str, filename_root: str, filename_ext: str, code_content: str, sdk_url: str ) -> List[str]: # write code file and metadata file code_filename = filename_root + filename_ext metadata_filename = filename_root + ".json" metadata_json = {"sdkUrl": sdk_url} target_dir_path = path.join(sdk_examples_path, target_dir) os.makedirs(target_dir_path, exist_ok=True) code_file_path = path.join(target_dir_path, code_filename) with open(code_file_path, "w", encoding="utf-8") as f: f.write(code_content) logging.info(f"Code written to file: {code_file_path}") metadata_file_path = path.join(target_dir_path, metadata_filename) with open(metadata_file_path, "w", encoding="utf-8") as f: json.dump(metadata_json, f) logging.info(f"Metadata written to file: {metadata_file_path}") return [path.join(target_dir, code_filename), path.join(target_dir, metadata_filename)] def create_dotnet_examples( release: Release, dotnet_module: str, sdk_examples_path: str, dotnet_examples_path: str ) -> (bool, List[str]): dotnet_paths = [] for root, dirs, files in os.walk(dotnet_examples_path): for name in files: filepath = path.join(root, name) if path.splitext(filepath)[1] == ".cs": dotnet_paths.append(filepath) logging.info(f"Processing SDK examples: {release.package}") dotnet_examples = [] for filepath in dotnet_paths: dotnet_examples += process_dotnet_example(filepath) files = [] if dotnet_examples: dotnet_build = DotNetBuild(tmp_path, dotnet_module.split(",")[0], dotnet_module.split(",")[1], dotnet_examples) build_result = dotnet_build.build() if build_result.succeeded: files = generate_examples(release, sdk_examples_path, dotnet_examples) else: logging.error("Build failed") return build_result.succeeded, files else: logging.info("SDK examples not found") return True, files def get_module_relative_path(sdk_name: str, sdk_path: str) -> str: global module_relative_path candidate_sdk_paths = glob.glob(path.join(sdk_path, f"sdk/*/{sdk_name}")) if len(candidate_sdk_paths) > 0: candidate_sdk_paths = [path.relpath(p, sdk_path) for p in candidate_sdk_paths] logging.info(f"Use first item of {candidate_sdk_paths} for SDK folder") module_relative_path = candidate_sdk_paths[0] else: raise RuntimeError(f"Source folder not found for SDK {sdk_name}") return module_relative_path def main(): global script_path global tmp_path global specs_path global sdk_package_path logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %X") script_path = path.abspath(path.dirname(sys.argv[0])) parser = argparse.ArgumentParser(description='Requires 2 arguments, path of "input.json" and "output.json".') parser.add_argument("paths", metavar="path", type=str, nargs=2, help='path of "input.json" or "output.json"') args = parser.parse_args() input_json_path = args.paths[0] output_json_path = args.paths[1] with open(input_json_path, "r", encoding="utf-8") as f_in: config = json.load(f_in) specs_path = config["specsPath"] sdk_path = config["sdkPath"] sdk_examples_path = config["sdkExamplesPath"] tmp_path = config["tempPath"] release = Release(config["release"]["tag"], config["release"]["package"], config["release"]["version"]) # samples/Generated/Samples module_relative_path_local = get_module_relative_path(release.package, sdk_path) dotnet_examples_relative_path = path.join(module_relative_path_local, "samples", "Generated", "Samples") dotnet_examples_path = path.join(sdk_path, dotnet_examples_relative_path) if not path.exists(dotnet_examples_path): # fallback to tests/Generated/Samples dotnet_examples_relative_path = path.join(module_relative_path_local, "tests", "Generated", "Samples") dotnet_examples_path = path.join(sdk_path, dotnet_examples_relative_path) sdk_package_path = path.join(sdk_path, module_relative_path_local) dotnet_module = f"{release.package},{release.version}" succeeded, files = create_dotnet_examples(release, dotnet_module, sdk_examples_path, dotnet_examples_path) with open(output_json_path, "w", encoding="utf-8") as f_out: output = {"status": "succeeded" if succeeded else "failed", "name": dotnet_module, "files": files} json.dump(output, f_out, indent=2) if __name__ == "__main__": main()