diff --git a/README.md b/README.md
index 6f430f6..082c078 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+---
+warn: This is just a prototype.
+---
+
# YASD, Yet Another Schema Definition
YASD is a YAML format for human writable XSDs (XML Schema Definition), humans
@@ -22,7 +26,7 @@ flowchart LR
sample --- |from| YASD3([YASD])
YASD3 --> |to| XML([XML])
document --- |from| YASD4([YASD])
- YASD4 --> |to| MD([Markdown])
+ YASD4 --> |to| RST([reStructuredText])
man --- |prints| README
```
@@ -32,7 +36,7 @@ To better understad what YASD does, see the inputs and outputs:
- Input: human workable schema in [YASD].
- Output: computer processable schema in [XSD].
-- Output: human readable documentation in [Markdown].
+- Output: human readable documentation in [reStructuredText][RST].
## Table of Contents
@@ -399,6 +403,6 @@ Mandatory.
[YASD]: https://gitlab.com/amlengua/apal/esquema/-/blob/main/apal.yaml
[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/
[W3Schools]: https://www.w3schools.com/xml/schema_intro.asp
diff --git a/yasd.py b/yasd.py
index 8c4755e..bd10483 100755
--- a/yasd.py
+++ b/yasd.py
@@ -13,62 +13,95 @@ from bs4.formatter import XMLFormatter
class YASD:
"""
- Performs YASD actions.
+ YASD actions performer.
"""
- def do(quiet, log, samples, action, filepath):
- yasd = YASD(quiet, log, samples, filepath)
- if action == "convert":
- yasd.convert()
- elif action == "sample":
- yasd.sample()
- elif action == "document":
- yasd.document()
+ def do(action="check", indata=None, outfile=None, quiet=False, log=False):
+ """
+ Performs YASD actions directly.
- 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.
+ :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: False or True
+ :type quiet: True or False
:param log: If messages are write in a file or not; 'False' by default
- :type log: False or True
- :param int samples: Quantity of XML samples; '1' by default
- :param filepath: YASD file path; 'None' by default
- :type filepath: None or Path
+ :type log: True or False
"""
self.msgr = YASDMessenger(quiet=quiet, log=log)
- valid_input = YASDCheck(self.msgr, filepath)
- self.filepath = valid_input.filepath
- self.yaml = valid_input.yaml
- self.samples = samples
- self.soups = {
- "schema": "",
- "elements": "",
- "attributes": "",
- "groups": "",
- }
+ self.yaml = YASDCheck(indata, self.msgr).yaml
+ self.formatter = XMLFormatter(indent=2)
+ self.soups = self.__get_soups()
+ self.outfile = outfile
+ self.out = ""
- def convert(self):
+ def convert(self, stdout=False):
"""
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_elements()
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.
- """
- print(f"TODO: {self.samples} samples")
+ Generates XML sample from YASD.
- 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):
unwanted = "version schemaLocation".split()
@@ -159,6 +192,17 @@ class YASD:
del el["description"]
return el
+ def __get_soups(self):
+ """
+ Gets soups structures.
+ """
+ return {
+ "schema": "",
+ "elements": "",
+ "attributes": "",
+ "groups": "",
+ }
+
def __get_base(self, key):
"""
Gets restriction data type.
@@ -173,77 +217,129 @@ class YASD:
else:
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")
- formatter = XMLFormatter(indent=2)
+ xsd = self.soups["schema"]
+ del self.soups["schema"]
for key, val in self.soups.items():
- if key == "schema":
- xsd = val.schema
- else:
- xsd.append(val)
- filename.write_text(xsd.prettify(formatter=formatter))
+ xsd.append(val)
+ self.out = xsd.prettify(formatter=self.formatter)
+
+ def __output(self, extname=".xsd"):
+ """
+ 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:
"""
- Verifies YASD file.
+ YASD input validator.
"""
- def __init__(self, messenger, filepath):
+ def __init__(self, indata=None, messenger=None):
"""
Inits YASD validator.
- :param YASDMessenger messenger: Object for print or save messages
- :param filepath: YASD file path
- :type filepath: None or Path
+ :param indata: YASD input
+ :type indata: None or Path or dict
+ :param messenger: Object for print or save messages
+ :type messenger: None or YASDMessenger
"""
- self.msgr = messenger
- self.__check_file(filepath)
- self.__parse_file()
- # TODO: do extra checks
+ if messenger is None:
+ self.msgr = YASDMessenger()
+ else:
+ 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.
:param filepath: YASD file path
:type filepath: None or Path
"""
- if filepath is None:
+ if type(filepath).__module__ != "pathlib":
self.msgr.run("no_input", level="error")
elif not filepath.exists() or not filepath.is_file():
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.
+
+ :param filepath: YASD file path
+ :type filepath: Path
"""
- raw = self.filepath.read_text(encoding="utf8")
+ raw = filepath.read_text(encoding="utf8")
try:
- self.yaml = yaml.safe_load(raw)
+ return yaml.safe_load(raw)
except yaml.YAMLError:
- # TODO: should be log class
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:
"""
- Prints or saves YASD messages.
+ YASD printer or writer.
"""
def keys():
"""
Messages keys dictionary.
+
+ Here multilang support could be implemented with:
+ https://github.com/sectasy0/pyi18n
"""
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 .
+ """,
+ "epilog": """
+ (c) 2023 Perro Tuerto . Founded by Mexican
+ Academy of Language . Licensed under GPLv3
+ .
+ """,
+ "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'",
- "no_input": "Input file needed.",
"invalid_input": "Invalid file '@file'",
"invalid_yaml": "Invalid YAML structure",
+ "no_input": "Input file needed.",
}
def __init__(self, quiet=False, log=False):
@@ -278,7 +374,7 @@ class YASDMessenger:
: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)
def __get_msg(self, key, **kwargs):
@@ -300,49 +396,59 @@ class YASDMessenger:
return key
-def main():
+class YASDCLI:
"""
- Gets and parses argv, then calls YASD.
+ YASD command-line interface.
"""
- parser = argparse.ArgumentParser(
- prog="yasd",
- 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 .
- """,
- epilog="""
- (c) 2023 Perro Tuerto .
- Founded by Mexican Academy of Language .
- Licensed under GPLv3 .
- """,
- )
- parser.add_argument(
- "action",
- choices=["convert", "check", "sample", "document", "man"],
- help="action to perform",
- )
- parser.add_argument(
- "file",
- type=Path,
- nargs="?",
- default=None,
- help="file in YAML format",
- )
- parser.add_argument(
- "-q", "--quiet", action="store_true", help="enable quiet mode"
- )
- parser.add_argument("-l", "--log", action="store_true", help="write log")
- parser.add_argument(
- "-n", "--num", default=1, help="number of XML samples; 1 by default"
- )
- args = parser.parse_args()
- if args.action == "man":
- print("MAN")
- else:
- YASD.do(args.quiet, args.log, args.num, args.action, args.file)
+
+ def __init__(self):
+ """
+ Inits YASD CLI.
+ """
+ self.__init_parser()
+ args = self.parser.parse_args()
+ if args.action == "man":
+ # TODO: print man from README
+ print("TODO: MAN")
+ else:
+ YASD.do(args.action, args.input, args.output, args.quiet, args.log)
+
+ def __init_parser(self):
+ """
+ Inits argument parser.
+ """
+ msg = YASDMessenger.keys()
+ self.parser = argparse.ArgumentParser(
+ prog="yasd",
+ description=msg["description"],
+ epilog=msg["epilog"],
+ )
+ self.parser.add_argument(
+ "action",
+ choices=["convert", "check", "sample", "document", "man"],
+ help=msg["help_action"],
+ )
+ self.parser.add_argument(
+ "input",
+ type=Path,
+ nargs="?",
+ default=None,
+ help=msg["help_input"],
+ )
+ self.parser.add_argument(
+ "-q", "--quiet", action="store_true", help=msg["help_quiet"]
+ )
+ 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__":
- main()
+ YASDCLI()