tools/azure-rest-api-specs-examples-automation/go/main.py (239 lines of code) (raw):
import sys
import urllib.parse
import os
from os import path
import json
import argparse
import logging
import dataclasses
from typing import List
import importlib.util
from models import GoExample, GoVetResult
from validate import GoVet
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 = "// Generated from example definition: "
@dataclasses.dataclass(eq=True, frozen=True)
class Release:
tag: str
package: str
version: str
@dataclasses.dataclass(eq=True)
class GoExampleMethodContent:
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 AggregatedGoExample:
methods: List[GoExampleMethodContent]
class_opening: List[str] = None
def is_aggregated_go_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 parse_original_file(original_file: str) -> str:
if original_file.startswith("https://"):
spec_main_segment = "https://github.com/Azure/azure-rest-api-specs/tree/main/"
if original_file.startswith(spec_main_segment):
original_file = original_file[len(spec_main_segment) :]
else:
specification_index = original_file.find("specification/")
if specification_index != -1:
original_file = original_file[specification_index:]
else:
logging.error(f"Parse relative path from URI {original_file} failed")
original_file = None
return original_file
def get_go_example_method(lines: List[str], start: int) -> GoExampleMethodContent:
# extract one example method, start from certain line number
original_file = None
go_example_method = GoExampleMethodContent()
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) :]
original_file = parse_original_file(original_file)
elif line.startswith("func "):
# begin of method
go_example_method.example_relative_path = original_file
go_example_method.line_start = index
elif line.startswith("}"):
# end of method
go_example_method.line_end = index + 1
break
if go_example_method.is_valid():
# backtrace to include comments before the method declaration
for index in range(go_example_method.line_start - 1, start - 1, -1):
line = lines[index]
if line.strip().startswith("//"):
go_example_method.line_start = index
else:
break
go_example_method.content = lines[go_example_method.line_start : go_example_method.line_end]
return go_example_method
def break_down_aggregated_go_example(lines: List[str]) -> AggregatedGoExample:
# break down sample Go to multiple examples
aggregated_go_example = AggregatedGoExample([])
go_example_method = get_go_example_method(lines, 0)
line_start = go_example_method.line_start
line_end = go_example_method.line_end
while go_example_method.is_valid():
aggregated_go_example.methods.append(go_example_method)
line_end = go_example_method.line_end
go_example_method = get_go_example_method(lines, go_example_method.line_end)
aggregated_go_example.class_opening = lines[0:line_start]
aggregated_go_example.class_closing = lines[line_end:]
return aggregated_go_example
def format_go(lines: List[str]) -> List[str]:
# format example as Go code
new_lines = []
skip_head = True
for line in lines:
if not skip_head:
new_lines.append(line)
else:
# start with package
if line.startswith("package "):
new_lines.append(line)
skip_head = False
return new_lines
def process_go_example(filepath: str) -> List[GoExample]:
# process aggregated Go sample to examples
filename = path.basename(filepath)
logging.info(f"Processing Go aggregated sample: {filename}")
with open(filepath, encoding="utf-8") as f:
lines = f.readlines()
go_examples = []
if is_aggregated_go_example(lines):
aggregated_go_example = break_down_aggregated_go_example(lines)
for go_example_method in aggregated_go_example.methods:
if go_example_method.is_valid():
logging.info(f"Processing Go example: {go_example_method.example_relative_path}")
# re-construct the example class, from example method
example_lines = aggregated_go_example.class_opening + go_example_method.content
example_filepath = go_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
example_lines = format_go(example_lines)
filename = example_filename.split(".")[0]
# use the examples-go folder for Go example
md_dir = (
(example_dir + "-go")
if example_dir.endswith("/examples")
else example_dir.replace("/examples/", "/examples-go/")
)
go_example = GoExample(filename, md_dir, "".join(example_lines))
go_examples.append(go_example)
return go_examples
def validate_go_examples(go_module: str, go_mod_filepath: str, go_examples: List[GoExample]) -> GoVetResult:
# batch validate Go examples
go_mod = None
if path.isfile(go_mod_filepath):
with open(go_mod_filepath, encoding="utf-8") as f:
go_mod = f.read()
go_vet = GoVet(tmp_path, go_module, go_mod, go_examples)
go_vet_result = go_vet.vet()
return go_vet_result
def generate_examples(release: Release, sdk_examples_path: str, go_examples: List[GoExample]) -> List[str]:
# generate code and metadata from Go examples
files = []
for go_example in go_examples:
escaped_release_tag = urllib.parse.quote(release.tag, safe="")
doc_link = (
f"https://github.com/Azure/azure-sdk-for-go/blob/{escaped_release_tag}/" f"{release.package}/README.md"
)
files.extend(
write_code_to_file(
sdk_examples_path,
go_example.target_dir,
go_example.target_filename,
".go",
go_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_go_examples(
release: Release, go_module: str, go_mod_filepath: str, sdk_examples_path: str, go_examples_path: str
) -> (bool, List[str]):
go_paths = []
for root, dirs, files in os.walk(go_examples_path):
for name in files:
filepath = path.join(root, name)
if path.splitext(filepath)[1] == ".go" and filepath.endswith("_test.go"):
go_paths.append(filepath)
logging.info(f"Processing SDK examples: {release.package}")
go_examples = []
for filepath in go_paths:
go_examples += process_go_example(filepath)
files = []
if go_examples:
logging.info("Validating SDK examples")
go_vet_result = validate_go_examples(go_module, go_mod_filepath, go_examples)
if go_vet_result.succeeded:
files = generate_examples(release, sdk_examples_path, go_vet_result.examples)
else:
logging.error("Validation failed")
return go_vet_result.succeeded, files
else:
logging.info("SDK examples not found")
return True, files
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"])
go_module_major_suffix = (
""
if release.version.startswith("v0.") or release.version.startswith("v1.")
else f'/{release.version.split(".")[0]}'
)
go_module = f"github.com/Azure/azure-sdk-for-go/{release.package}{go_module_major_suffix}@{release.version}"
go_examples_relative_path = release.package
go_examples_path = path.join(sdk_path, go_examples_relative_path)
go_mod_filepath = path.join(sdk_path, release.package, "go.mod")
sdk_package_path = path.join(sdk_path, release.package)
succeeded, files = create_go_examples(release, go_module, go_mod_filepath, sdk_examples_path, go_examples_path)
with open(output_json_path, "w", encoding="utf-8") as f_out:
output = {"status": "succeeded" if succeeded else "failed", "name": go_module, "files": files}
json.dump(output, f_out, indent=2)
if __name__ == "__main__":
main()