Todo perfilado para continuar con builds
This commit is contained in:
parent
247f05f851
commit
6e1ed70a78
10
README.md
10
README.md
|
@ -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
312
yasd.py
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue