Skip to content

Commit

Permalink
Add entry point of documentation with the markdown exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
NahuFigueroa97 committed Jul 26, 2024
1 parent 0e22fa6 commit d7123a4
Show file tree
Hide file tree
Showing 22 changed files with 720 additions and 98 deletions.
1 change: 1 addition & 0 deletions src/engine/test/helper_tests/engine_helper_test/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ console_scripts =
engine-helper-test-initial-state = initial_state.__main__:main
engine-helper-test-runner = runner.__main__:main
engine-helper-test-generate-runner = generator_runner.__main__:main
engine-helper-test-documentation = documentation_generator.__main__:main

[options.extras_require]
dev =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,26 +132,27 @@ def check_restrictions(arguments: list, general_restrictions: list, input: dict
bool: True if the arguments meet the restrictions, False otherwise.
"""
for general_restriction in general_restrictions:
for key, value in general_restriction.items():
if input:
# Check both input and arguments
input_values = input.values()
try:
eval_argument = ast.literal_eval(arguments[key - 1])
except (ValueError, SyntaxError):
eval_argument = arguments[key - 1] # Use the raw string if eval fails

if eval_argument != value and value not in input_values:
break
if general_restriction:
for index, (key, value) in enumerate(general_restriction.items()):
if input:
# Check both input and arguments
input_values = input.values()
try:
eval_argument = ast.literal_eval(arguments[index])
except (ValueError, SyntaxError):
eval_argument = arguments[index] # Use the raw string if eval fails

if eval_argument != value and value not in input_values:
break
else:
# Check only arguments
try:
eval_argument = ast.literal_eval(arguments[index])
except (ValueError, SyntaxError):
eval_argument = arguments[index] # Use the raw string if eval fails

if eval_argument != value:
break
else:
# Check only arguments
try:
eval_argument = ast.literal_eval(arguments[key - 1])
except (ValueError, SyntaxError):
eval_argument = arguments[key - 1] # Use the raw string if eval fails

if eval_argument != value:
break
else:
return True
return True
return False
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3

import argparse
import sys
from pathlib import Path
from test_cases_generator.parser import Parser
from .documentation import *
from .exporter import IExporter
from .types import *
import tempfile


def parse_arguments() -> argparse.Namespace:
arg_parser = argparse.ArgumentParser(description="Generates files containing test cases for a given helper")
arg_parser.add_argument(
"--input_file_path",
help="Absolute or relative path where the description of the helper function is located",
)
arg_parser.add_argument(
"--folder_path",
help="Absolute or relative path where the directory that contains the descriptions of the auxiliary functions is located",
)
arg_parser.add_argument(
"--exporter",
help="Absolute or relative path of the directory where the generated test files will be located",
)
arg_parser.add_argument(
"-o",
"--output_path",
help="Absolute or relative path of the directory where the generated test files will be located",
)

args = arg_parser.parse_args()

if args.input_file_path and args.folder_path:
arg_parser.error("Only one of --input_file_path or --folder_path can be specified.")

return args


def is_temp_path(path_str):
path = Path(path_str).resolve()
temp_dir = Path(tempfile.gettempdir()).resolve()
return str(path).startswith(str(temp_dir))


def generate_documentation(parser: Parser, exporter: IExporter, file: Path, output_dir: Path):
parser.load_yaml_from_file(file)
documentation = parse_yaml_to_documentation(parser)
exporter.create_document(documentation)
exporter.save(output_dir)


def main():
args = parse_arguments()
parser = Parser()
exporter_type = args.exporter if args.exporter else "mark_down"
output_dir = Path(args.output_path if args.output_path else "/tmp/documentation")
exporter = ExporterFactory.get_exporter(exporter_type)
if args.input_file_path:
generate_documentation(parser, exporter, Path(args.input_file_path), output_dir)
elif args.folder_path:
for file in Path(args.folder_path).iterdir():
if file.is_file() and (file.suffix in ['.yml', '.yaml']):
generate_documentation(parser, exporter, file, output_dir)
else:
sys.exit("It is necessary to indicate a file or directory that contains a configuration yaml")

print("Success validation")
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from .html_generator import HTMLGenerator
from .pdf_generator import PDFGenerator
from .mark_down_generator import MarkdownGenerator
from .exporter import *
from .types import *
from test_cases_generator.parser import Parser


class ExporterFactory():
@staticmethod
def get_exporter(type_: str) -> IExporter:
if convert_str_to_exporter_type(type_) == ExporterType.PDF:
return PDFGenerator()
elif convert_str_to_exporter_type(type_) == ExporterType.HTML:
return HTMLGenerator()
elif convert_str_to_exporter_type(type_) == ExporterType.MARK_DOWN:
return MarkdownGenerator()


def parse_yaml_to_documentation(parser: Parser):
metadata = Metadata(
parser.get_metadata()["description"],
parser.get_metadata()["keywords"])

arguments = {name: Argument(parser.get_types()[index], parser.get_sources()[index], parser.get_subset(
)[index], parser.get_restrictions()[index]) for index, (name, arg_info) in enumerate(parser.get_arguments().items())}

output = Output(parser.get_output()["type"], parser.get_output().get("subset"))

examples = [
Example(
test['arguments'],
test['should_pass'],
test['description'],
test.get('skipped', False),
test.get('expected')) for test in parser.get_tests()]

documentation = Documentation(
name=parser.get_name(),
helper_type=parser.get_helper_type(),
is_variadic=parser.is_variadic(),
metadata=metadata,
arguments=arguments,
output=output,
general_restrictions=parser.get_general_restrictions_details(),
examples=examples
)

return documentation
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from abc import ABC, abstractmethod
from enum import Enum
from pathlib import Path
from .types import Documentation


class ExporterType(Enum):
PDF = 1,
HTML = 2,
MARK_DOWN = 3


def convert_str_to_exporter_type(type_: str):
if type_ == "pdf":
return ExporterType.PDF
elif type_ == "html":
return ExporterType.HTML
elif type_ == "mark_down":
return ExporterType.MARK_DOWN
else:
raise ValueError(f"Exporter type {type_} does not exist")


def convert_exporter_type_to_str(exporter_type: ExporterType):
if exporter_type == convert_str_to_exporter_type("pdf"):
return "pdf"
elif exporter_type == convert_str_to_exporter_type("pdf"):
return "html"
elif exporter_type == convert_str_to_exporter_type("html"):
return "mark_down"
else:
raise ValueError(f"Exporter type does not exist")


class IExporter(ABC):
@abstractmethod
def create_document(self, doc: Documentation):
pass

@abstractmethod
def save(self, output_dir: Path):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .types import Documentation
from .exporter import *


class HTMLGenerator(IExporter):
def __init__(self):
self.content = []
self.helper_name = ""

def create_document(self, doc: Documentation):
pass

def save(self, output_dir: Path):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from .types import Documentation
from .exporter import *


class MarkdownGenerator(IExporter):
def __init__(self):
self.content = []
self.helper_name = ""

def create_signature(self, doc: Documentation):
self.content.append(f"```\n")
if not doc.is_variadic:
self.content.append(f"field: {doc.name}({', '.join(str(v) for v in doc.arguments)})")
else:
self.content.append(f"field: {doc.name}({', '.join(str(v) for v in doc.arguments)}, [...])")
self.content.append(f"```\n")

def create_table(self, arguments: dict, headers: list):
"""
Create a table in Markdown format.
:param arguments: Dictionary of arguments.
:return: Table string in Markdown format.
"""
# Create the header row
header_row = '| ' + ' | '.join(headers) + ' |'
separator_row = '| ' + ' | '.join(['-' * len(header) for header in headers]) + ' |'

# Create the data rows
rows = []
for name, info in arguments.items():
row = [] # Create a new list for each argument
row.append(name)

# Handle arg_type being a list or a string
if isinstance(info.arg_type, list):
row.append(', '.join(info.arg_type))
else:
row.append(info.arg_type)

if info.source == "both":
row.append("value or reference")
else:
row.append(info.source)

if info.restrictions:
row.append(', '.join(info.restrictions["allowed"]) if isinstance(
info.restrictions["allowed"], list) else str(info.restrictions["allowed"]))
else:
if info.arg_type == "object":
row.append("Any object")
elif info.arg_type == "array":
row.append("Any array")
elif info.generate == "integer":
row.append("Integers between `-2^63` and `2^63-1`")
elif info.generate == "string":
row.append("Any string")
elif info.generate == "ip":
row.append("Any IP")

rows.append(row)
data_rows = ['| ' + ' | '.join(row) + ' |' for row in rows]

# Merge all rows
self.content.append('\n'.join([header_row, separator_row] + data_rows))
self.content.append("\n")

def create_output_table(self, output, headers: list):
"""
Create a table in Markdown format.
:param arguments: Dictionary of arguments.
:return: Table string in Markdown format.
"""
# Create the header row
header_row = '| ' + ' | '.join(headers) + ' |'
separator_row = '| ' + ' | '.join(['-' * len(header) for header in headers]) + ' |'

# Create the data rows
rows = []
row = [] # Create a new list for each argument
row.append(output.type_)
if output.type_ == "object":
row.append("Any object")
elif output.type_ == "array":
row.append("Any array")
elif output.subset == "integer":
row.append("Integers between `-2^63` and `2^63-1`")
elif output.subset == "string":
row.append("Any string")
elif output.subset == "ip":
row.append("Any IP")
rows.append(row)
data_rows = ['| ' + ' | '.join(row) + ' |' for row in rows]

# Merge all rows
self.content.append('\n'.join([header_row, separator_row] + data_rows))
self.content.append("\n")

def create_document(self, doc: Documentation):
self.content = []
self.helper_name = doc.name
self.content.append(f"# {doc.name}\n")

self.content.append(f"## Signature\n")
self.create_signature(doc)

self.content.append(f"## Arguments\n")
headers = ["parameter", "Type", "Source", "Accepted values"]
self.create_table(doc.arguments, headers)

self.content.append(f"## Outputs\n")
headers = ["Type", "Posible values"]
self.create_output_table(doc.output, headers)

self.content.append(f"## Description\n")
self.content.append(f"{doc.description}\n")
self.content.append(f"**Keywords**\n")
for keyword in doc.keywords:
self.content.append(f"- `{keyword}` \n")

if doc.general_restrictions:
self.content.append(f"## Notes\n")
for general_restriction in doc.general_restrictions:
self.content.append(f"- {general_restriction}\n")

# self.content.append("\n### Examples\n")
# for idx, example in enumerate(doc.examples, start=1):
# self.content.append(f"**Example {idx}**:")
# self.content.append(" - **Arguments**:")
# for arg, value in example.arguments.items():
# self.content.append(f" - `{arg}`: `{value}`")
# self.content.append(f" - **Should Pass**: `{example.should_pass}`")
# if example.skipped:
# self.content.append(f" - **Skipped**: `{example.skipped}`")
# if example.expected is not None:
# self.content.append(f" - **Expected**: `{example.expected}`")
# self.content.append(f" - **Description**: {example.description}\n")

def save(self, output_dir: Path):
output_dir.mkdir(parents=True, exist_ok=True)
with open((output_dir / self.helper_name).as_posix() + ".md", 'w') as file:
file.write('\n'.join(self.content))
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .types import Documentation
from .exporter import *


class PDFGenerator(IExporter):
def __init__(self):
self.content = []
self.helper_name = ""

def create_document(self, doc: Documentation):
pass

def save(self, output_dir: Path):
pass
Loading

0 comments on commit d7123a4

Please sign in to comment.