Todo perfilado para continuar con builds

This commit is contained in:
perro tuerto 2023-01-26 11:15:08 -08:00
parent 247f05f851
commit 6e1ed70a78
2 changed files with 216 additions and 106 deletions

View File

@ -1,3 +1,7 @@
---
warn: This is just a prototype.
---
# YASD, Yet Another Schema Definition # YASD, Yet Another Schema Definition
YASD is a YAML format for human writable XSDs (XML Schema Definition), humans YASD is a YAML format for human writable XSDs (XML Schema Definition), humans
@ -22,7 +26,7 @@ flowchart LR
sample --- |from| YASD3([YASD]) sample --- |from| YASD3([YASD])
YASD3 --> |to| XML([XML]) YASD3 --> |to| XML([XML])
document --- |from| YASD4([YASD]) document --- |from| YASD4([YASD])
YASD4 --> |to| MD([Markdown]) YASD4 --> |to| RST([reStructuredText])
man --- |prints| README man --- |prints| README
``` ```
@ -32,7 +36,7 @@ To better understad what YASD does, see the inputs and outputs:
- Input: human workable schema in [YASD]. - Input: human workable schema in [YASD].
- Output: computer processable schema in [XSD]. - Output: computer processable schema in [XSD].
- Output: human readable documentation in [Markdown]. - Output: human readable documentation in [reStructuredText][RST].
## Table of Contents ## Table of Contents
@ -399,6 +403,6 @@ Mandatory.
[YASD]: https://gitlab.com/amlengua/apal/esquema/-/blob/main/apal.yaml [YASD]: https://gitlab.com/amlengua/apal/esquema/-/blob/main/apal.yaml
[XSD]: https://gitlab.com/amlengua/apal/esquema/-/blob/main/apal.xsd [XSD]: https://gitlab.com/amlengua/apal/esquema/-/blob/main/apal.xsd
[Markdown]: https://gitlab.com/amlengua/apal/esquema/-/blob/main/apal.md [RST]: https://gitlab.com/amlengua/apal/esquema/-/blob/main/apal.rst
[Tutorials Point]: https://www.tutorialspoint.com/xsd/ [Tutorials Point]: https://www.tutorialspoint.com/xsd/
[W3Schools]: https://www.w3schools.com/xml/schema_intro.asp [W3Schools]: https://www.w3schools.com/xml/schema_intro.asp

312
yasd.py
View File

@ -13,62 +13,95 @@ from bs4.formatter import XMLFormatter
class YASD: class YASD:
""" """
Performs YASD actions. YASD actions performer.
""" """
def do(quiet, log, samples, action, filepath): def do(action="check", indata=None, outfile=None, quiet=False, log=False):
yasd = YASD(quiet, log, samples, filepath) """
if action == "convert": Performs YASD actions directly.
yasd.convert()
elif action == "sample":
yasd.sample()
elif action == "document":
yasd.document()
def __init__(self, quiet=False, log=False, samples=1, filepath=None): Intented for YASDCLI, but can also be used programmatically.
:param str action: YASD action to perform; 'check' by default
:param indata: YASD input; 'None' by default
:type indata: None or Path or dict
:param outfile: YASD output file path; 'None' by default
:type outfile: None or Path
:param quiet: If messages are print or not; 'False' by default
:type quiet: True or False
:param log: If messages are write in a file or not; 'False' by default
:type log: True or False
"""
yasd = YASD(indata, outfile, quiet, log)
yasd.msgr.run(f"action_{action}")
if action == "convert":
yasd.convert(stdout=True)
elif action == "sample":
yasd.sample(stdout=True)
elif action == "document":
yasd.document(stdout=True)
def __init__(self, indata=None, outfile=None, quiet=False, log=False):
""" """
Inits YASD object. Inits YASD object.
:param indata: YASD input; 'None' by default
:type indata: None or Path or dict
:param outfile: YASD output file path; 'None' by default
:type outfile: None or Path
:param quiet: If messages are print or not; 'False' by default :param quiet: If messages are print or not; 'False' by default
:type quiet: False or True :type quiet: True or False
:param log: If messages are write in a file or not; 'False' by default :param log: If messages are write in a file or not; 'False' by default
:type log: False or True :type log: True or False
:param int samples: Quantity of XML samples; '1' by default
:param filepath: YASD file path; 'None' by default
:type filepath: None or Path
""" """
self.msgr = YASDMessenger(quiet=quiet, log=log) self.msgr = YASDMessenger(quiet=quiet, log=log)
valid_input = YASDCheck(self.msgr, filepath) self.yaml = YASDCheck(indata, self.msgr).yaml
self.filepath = valid_input.filepath self.formatter = XMLFormatter(indent=2)
self.yaml = valid_input.yaml self.soups = self.__get_soups()
self.samples = samples self.outfile = outfile
self.soups = { self.out = ""
"schema": "",
"elements": "",
"attributes": "",
"groups": "",
}
def convert(self): def convert(self, stdout=False):
""" """
Converts YASD to XSD. Converts YASD to XSD.
:param stdout: if conversion goes to stdout or not; 'False' by default
:type stdout: True or False
""" """
self.__build_schema() self.__build_schema()
self.__build_elements() self.__build_elements()
self.__build_attributes() self.__build_attributes()
self.__write() self.__stringify_xsd()
if stdout:
self.__output()
else:
return self.out
def sample(self): def sample(self, stdout=False):
""" """
Generates XML samples from YASD. Generates XML sample from YASD.
"""
print(f"TODO: {self.samples} samples")
def document(self): :param stdout: if sample goes to stdout or not; 'False' by default
:type stdout: True or False
""" """
Generates MD documentation self.out = "TODO: XML sample"
if stdout:
self.__output()
else:
return self.out
def document(self, stdout=False):
""" """
print("TODO: MD document from :", self.__dict__) Generates RST documentation
:param stdout: if document goes to stdout or not; 'False' by default
:type stdout: True or False
"""
self.out = f"TODO: RST document from :{self.__dict__}"
if stdout:
self.__output()
else:
return self.out
def __build_schema(self): def __build_schema(self):
unwanted = "version schemaLocation".split() unwanted = "version schemaLocation".split()
@ -159,6 +192,17 @@ class YASD:
del el["description"] del el["description"]
return el return el
def __get_soups(self):
"""
Gets soups structures.
"""
return {
"schema": "",
"elements": "",
"attributes": "",
"groups": "",
}
def __get_base(self, key): def __get_base(self, key):
""" """
Gets restriction data type. Gets restriction data type.
@ -173,77 +217,129 @@ class YASD:
else: else:
return "xs:integer" return "xs:integer"
def __write(self): def __stringify_xsd(self):
""" """
Writes XSD into a file. Converts BeautifulSoups to pretty text format.
""" """
filename = Path(self.filepath.parent / f"{self.filepath.stem}.xsd") xsd = self.soups["schema"]
formatter = XMLFormatter(indent=2) del self.soups["schema"]
for key, val in self.soups.items(): for key, val in self.soups.items():
if key == "schema": xsd.append(val)
xsd = val.schema self.out = xsd.prettify(formatter=self.formatter)
else:
xsd.append(val) def __output(self, extname=".xsd"):
filename.write_text(xsd.prettify(formatter=formatter)) """
Prints in the terminal or writes into a file.
"""
if self.outfile is None:
print(self.out)
else:
suffix = self.outfile.suffix
if len(suffix) > 0 and suffix == suffix.replace(" ", ""):
extname = suffix
filename = f"{self.outfile.stem}{extname}"
filename = Path(self.outfile.parent / filename)
filename.write_text(self.out)
class YASDCheck: class YASDCheck:
""" """
Verifies YASD file. YASD input validator.
""" """
def __init__(self, messenger, filepath): def __init__(self, indata=None, messenger=None):
""" """
Inits YASD validator. Inits YASD validator.
:param YASDMessenger messenger: Object for print or save messages :param indata: YASD input
:param filepath: YASD file path :type indata: None or Path or dict
:type filepath: None or Path :param messenger: Object for print or save messages
:type messenger: None or YASDMessenger
""" """
self.msgr = messenger if messenger is None:
self.__check_file(filepath) self.msgr = YASDMessenger()
self.__parse_file() else:
# TODO: do extra checks self.msgr = messenger
if type(indata) is dict:
self.yaml = indata
else:
self.yaml = self.parse_file(self.check_file(indata))
self.check_structure()
def __check_file(self, filepath): def check_file(self, filepath):
""" """
Verifies YASD file. Verifies YASD file.
:param filepath: YASD file path :param filepath: YASD file path
:type filepath: None or Path :type filepath: None or Path
""" """
if filepath is None: if type(filepath).__module__ != "pathlib":
self.msgr.run("no_input", level="error") self.msgr.run("no_input", level="error")
elif not filepath.exists() or not filepath.is_file(): elif not filepath.exists() or not filepath.is_file():
self.msgr.run("invalid_input", level="error", file=filepath) self.msgr.run("invalid_input", level="error", file=filepath)
self.filepath = filepath.resolve() return filepath.resolve()
def __parse_file(self): def parse_file(self, filepath):
""" """
Attempts YASD file parsing. Attempts YASD file parsing.
:param filepath: YASD file path
:type filepath: Path
""" """
raw = self.filepath.read_text(encoding="utf8") raw = filepath.read_text(encoding="utf8")
try: try:
self.yaml = yaml.safe_load(raw) return yaml.safe_load(raw)
except yaml.YAMLError: except yaml.YAMLError:
# TODO: should be log class
self.msgr.run("invalid_yaml", level="error") self.msgr.run("invalid_yaml", level="error")
def check_structure(self):
"""
Verifies YASD structure.
:return: YASD structure
:rtype: dict
"""
# TODO: extra checks for self.yaml
...
class YASDMessenger: class YASDMessenger:
""" """
Prints or saves YASD messages. YASD printer or writer.
""" """
def keys(): def keys():
""" """
Messages keys dictionary. Messages keys dictionary.
Here multilang support could be implemented with:
https://github.com/sectasy0/pyi18n
""" """
return { return {
"description": """
YASD, Yet Another Schema Definition. YASD is a YAML format for
human writable XSDs (XML Schema Definition), humans declare what is
indispensable, leaving the machines to do the rest of the
unreadable <syntaxis who_can_read_this="?" />.
""",
"epilog": """
(c) 2023 Perro Tuerto <hi@perrotuerto.blog>. Founded by Mexican
Academy of Language <https://academia.org.mx>. Licensed under GPLv3
<https://www.gnu.org/licenses/gpl-3.0.en.html>.
""",
"help_action": "action to perform",
"help_input": "input file in YAML format",
"help_output": "output file",
"help_quiet": "enable quiet mode",
"help_log": "write log",
"action_convert": "Creating XSD schema",
"action_check": "Checking YASD",
"action_sample": "Creating XML sample",
"action_document": "Creating RST documentation",
"invalid_level": "Invalid log level '@lvl'", "invalid_level": "Invalid log level '@lvl'",
"no_input": "Input file needed.",
"invalid_input": "Invalid file '@file'", "invalid_input": "Invalid file '@file'",
"invalid_yaml": "Invalid YAML structure", "invalid_yaml": "Invalid YAML structure",
"no_input": "Input file needed.",
} }
def __init__(self, quiet=False, log=False): def __init__(self, quiet=False, log=False):
@ -278,7 +374,7 @@ class YASDMessenger:
:param str level: Log level :param str level: Log level
""" """
if not level in ["trace", "debug", "info", "warn", "error", "fatal"]: if level not in ["trace", "debug", "info", "warn", "error", "fatal"]:
YASDMessenger().run("invalid_level", level="warn", lvl=level) YASDMessenger().run("invalid_level", level="warn", lvl=level)
def __get_msg(self, key, **kwargs): def __get_msg(self, key, **kwargs):
@ -300,49 +396,59 @@ class YASDMessenger:
return key return key
def main(): class YASDCLI:
""" """
Gets and parses argv, then calls YASD. YASD command-line interface.
""" """
parser = argparse.ArgumentParser(
prog="yasd", def __init__(self):
description=""" """
YASD, Yet Another Schema Definition. Inits YASD CLI.
YASD is a YAML format for human writable XSDs (XML Schema Definition), """
humans declare what is indispensable, leaving the machines to do the self.__init_parser()
rest of the unreadable <syntaxis who_can_read_this="?" />. args = self.parser.parse_args()
""", if args.action == "man":
epilog=""" # TODO: print man from README
(c) 2023 Perro Tuerto <hi@perrotuerto.blog>. print("TODO: MAN")
Founded by Mexican Academy of Language <https://academia.org.mx>. else:
Licensed under GPLv3 <https://www.gnu.org/licenses/gpl-3.0.en.html>. YASD.do(args.action, args.input, args.output, args.quiet, args.log)
""",
) def __init_parser(self):
parser.add_argument( """
"action", Inits argument parser.
choices=["convert", "check", "sample", "document", "man"], """
help="action to perform", msg = YASDMessenger.keys()
) self.parser = argparse.ArgumentParser(
parser.add_argument( prog="yasd",
"file", description=msg["description"],
type=Path, epilog=msg["epilog"],
nargs="?", )
default=None, self.parser.add_argument(
help="file in YAML format", "action",
) choices=["convert", "check", "sample", "document", "man"],
parser.add_argument( help=msg["help_action"],
"-q", "--quiet", action="store_true", help="enable quiet mode" )
) self.parser.add_argument(
parser.add_argument("-l", "--log", action="store_true", help="write log") "input",
parser.add_argument( type=Path,
"-n", "--num", default=1, help="number of XML samples; 1 by default" nargs="?",
) default=None,
args = parser.parse_args() help=msg["help_input"],
if args.action == "man": )
print("MAN") self.parser.add_argument(
else: "-q", "--quiet", action="store_true", help=msg["help_quiet"]
YASD.do(args.quiet, args.log, args.num, args.action, args.file) )
self.parser.add_argument(
"-l", "--log", action="store_true", help=msg["help_log"]
)
self.parser.add_argument(
"-o",
"--output",
type=Path,
default=None,
help=msg["help_output"],
)
if __name__ == "__main__": if __name__ == "__main__":
main() YASDCLI()