Skip to content

Commit

Permalink
Rework API and CLI
Browse files Browse the repository at this point in the history
Signed-off-by: Roberto Di Remigio <roberto.diremigio@gmail.com>
  • Loading branch information
robertodr committed Mar 23, 2019
1 parent 3b02c3c commit c01b439
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 84 deletions.
128 changes: 93 additions & 35 deletions parselglossy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,62 +29,120 @@
# -*- coding: utf-8 -*-
"""Top-level functions for parselglossy."""

from typing import IO, Any
import json
from pathlib import Path
from typing import Union

from .grammars import getkw
from .utils import JSONDict
from .validation import check_predicates, fix_defaults, is_template_valid, merge_ours
from .views import view_by_default, view_by_predicates, view_by_type
from .grammars import lexer
from .utils import ComplexEncoder, JSONDict, as_complex, path_resolver, read_yaml_file
from .validation import validate_from_dicts


def lex(*, in_str: IO[Any], grammar: str) -> JSONDict:
def lex(
*, infile: Union[str, Path], grammar: str, ir_file: Union[str, Path] = None
) -> JSONDict:
"""Run grammar of choice on input string.
Parameters
----------
in_str : str
The string to be parsed.
in_str : IO[Any]
The string to be parsed.
grammar : str
Grammar to be used.
Grammar to be used.
ir_file : Union[str, Path]
File to write intermediate representation to (JSON format).
None by default, which means file is not written out.
Returns
-------
The contents of the input string as a dictionary.
"""
if grammar == "getkw":
lexer = getkw.grammar()
else:
lexer = getkw.grammar(has_complex=True)
return lexer.parseString(in_str).asDict()

infile = path_resolver(infile)
if ir_file is not None:
ir_file = path_resolver(ir_file)

with infile.open("r") as f:
ir = lexer.lex_from_str(in_str=f, grammar=grammar, ir_file=ir_file)

return ir

def validate(*, ir: JSONDict, template: JSONDict) -> JSONDict:

def validate(
*,
infile: Union[str, Path],
fr_file: Union[str, Path] = None,
template: Union[str, Path]
) -> JSONDict:
"""Validate intermediate representation into final representation.
Parameters
----------
dumpir : bool
Whether to serialize FR to JSON. Location and name of file are
determined based on the input file.
ir : JSONDict
Intermediate representation of the input file.
infile : Union[str, Path]
The file with the intermediate representation (JSON format).
fr_file : Union[str, Path]
File to write final representation to (JSON format).
None by default, which means file is not written out.
template : Union[str, Path]
Which validation template to use.
"""

Returns
-------
fr : JSONDict
The validated input.
infile = path_resolver(infile)
with infile.open("r") as f:
ir = json.load(f, object_hook=as_complex)

Raises
------
:exc:`ParselglossyError`
"""
is_template_valid(template)
stencil = view_by_default(template)
types = view_by_type(template)
predicates = view_by_predicates(template)
template = path_resolver(template)
stencil = read_yaml_file(Path(template))

if fr_file is not None:
fr_file = path_resolver(fr_file)

fr = merge_ours(theirs=stencil, ours=ir)
fr = fix_defaults(fr, types=types)
check_predicates(fr, predicates=predicates)
fr = validate_from_dicts(ir=ir, template=stencil, fr_file=fr_file)

return fr


def parse(
*,
infile: Union[str, Path],
outfile: Union[str, Path] = None,
grammar: str,
template: Union[str, Path],
dump_ir: bool = False
) -> None:
"""Parse input file.
Parameters
----------
infile : Union[str, Path]
The input file to be parsed.
outfile : Union[str, Path]
The output file.
None by default, which means file name default to <infile>_fr.json
grammar : str
Which grammar to use.
template : Union[str, Path]
Which validation template to use.
write_ir_out : bool
Whether to write out the intermediate representation to file (JSON format).
False by default. If true the filename if <infile>_ir.json
"""

stem = infile.rsplit(".", 1)[0]

infile = path_resolver(infile)
if dump_ir:
ir_file = path_resolver(stem + "_ir.json")
else:
ir_file = None

ir = lex(infile=infile, outfile=ir_file, grammar=grammar)

template = path_resolver(template)

fr = validate(infile=ir_file, outfile=outfile, template=template)

if outfile is not None:
outfile = path_resolver(stem + "_fr.json")
with outfile.open("w") as out:
json.dump(fr, out, cls=ComplexEncoder)
52 changes: 29 additions & 23 deletions parselglossy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
import click

from . import __version__, api
from .read_yaml import read_yaml_file
from .utils import ComplexEncoder, as_complex
from .utils import ComplexEncoder, as_complex, read_yaml_file


@click.group()
Expand Down Expand Up @@ -80,13 +79,10 @@ def _lex(infile: str, outfile: str, grammar: str) -> None:
Which grammar to use.
"""

with Path(infile).open("r") as f:
ir = api.lex(in_str=f, grammar=grammar)

if not outfile:
outfile = infile.rsplit(".", 1)[0] + "_ir.json"
with Path(outfile).open("w") as out:
json.dump(ir, out, cls=ComplexEncoder)

api.lex(infile=infile, grammar=grammar, ir_file=outfile)


@click.command(name="validate")
Expand All @@ -98,6 +94,7 @@ def _lex(infile: str, outfile: str, grammar: str) -> None:
@click.option(
"--outfile",
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
default="",
help="name or path for the validated output JSON file",
metavar="<outfile>",
)
Expand All @@ -120,19 +117,14 @@ def _validate(infile: str, outfile: str, template: str) -> None:
template : str
Which validation template to use.
"""
with Path(infile).open("r") as f:
ir = json.load(f, object_hook=as_complex)

stencil = read_yaml_file(Path(template))
fr = api.validate(ir=ir, template=stencil)

if not outfile:
outfile = infile.rsplit(".", 1)[0] + "_ir.json"
with Path(outfile).open("w") as out:
json.dump(fr, out, cls=ComplexEncoder)
outfile = infile.rsplit(".", 1)[0] + "_fr.json"

api.validate(infile=infile, outfile=outfile, template=template)


@click.command()
@click.command(name="parse")
@click.argument(
"infile",
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
Expand All @@ -141,6 +133,7 @@ def _validate(infile: str, outfile: str, template: str) -> None:
@click.option(
"--outfile",
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
default="",
help="name or path for the parsed JSON output file",
metavar="<outfile>",
)
Expand All @@ -158,7 +151,16 @@ def _validate(infile: str, outfile: str, template: str) -> None:
metavar="<template>",
help="which validation template to use",
)
def parse(infile: str, outfile: str, grammar: str, template: str) -> None:
@click.option(
"--dump-ir/--no-dump-ir",
default=False,
help="whether to dump intermediate representation to JSON file",
show_default=True,
metavar="<dumpir>",
)
def _parse(
infile: str, outfile: str, grammar: str, template: str, dumpir: bool
) -> None:
"""Parse input file.
\b
Expand All @@ -179,12 +181,16 @@ def parse(infile: str, outfile: str, grammar: str, template: str) -> None:
The intermediate representation is saved to <outfile>_ir.json
"""

stem = infile.rsplit(".", 1)[0]
if not outfile:
outfile = stem + "_fr.json"
ir_file = stem + "_ir.json"
_lex(infile, Path(ir_file), grammar)
_validate(ir_file, outfile, template)
outfile = infile.rsplit(".", 1)[0] + "_fr.json"

api.parse(
infile=infile,
outfile=outfile,
grammar=grammar,
template=template,
dump_ir=dumpir,
)


@click.command(name="doc")
Expand Down Expand Up @@ -220,4 +226,4 @@ def _doc(doctype: str):
cli.add_command(_doc)
cli.add_command(_lex)
cli.add_command(_validate)
cli.add_command(parse)
cli.add_command(_parse)
59 changes: 44 additions & 15 deletions parselglossy/read_yaml.py → parselglossy/grammars/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,59 @@
#

# -*- coding: utf-8 -*-
"""Top-level functions for parselglossy."""

import json
from pathlib import Path
from typing import IO, Any, Union

import yaml
from . import getkw
from ..exceptions import ParselglossyError
from ..utils import ComplexEncoder, JSONDict, path_resolver

from .utils import JSONDict


def read_yaml_file(file_name: Path) -> JSONDict:
"""Reads a YAML file and returns it as a dictionary.
def lex_from_str(
*, in_str: IO[Any], grammar: str = "standard", ir_file: Union[str, Path] = None
) -> JSONDict:
"""Run grammar of choice on input string.
Parameters
----------
file_name: Path
Path object for the YAML file.
in_str : IO[Any]
The string to be parsed.
grammar : str
Grammar to be used. Defaults to "standard".
ir_file : Union[str, Path]
File to write intermediate representation to (JSON format).
None by default, which means file is not written out.
Returns
-------
d: JSONDict
A dictionary with the contents of the YAML file.
The contents of the input string as a dictionary.
Raises
------
:exc:`ParselglossyError`
"""
with file_name.open("r") as f:
try:
d = yaml.safe_load(f)
except yaml.YAMLError as e:
print(e)
return d

try:
lexer = dispatch_grammar(grammar)
except KeyError:
raise ParselglossyError("Grammar {} not available.".format(grammar))

ir = lexer.parseString(in_str).asDict()

if ir_file is not None:
ir_file = path_resolver(ir_file)
with ir_file.open("w") as out:
json.dump(ir, out, cls=ComplexEncoder)

return ir


def dispatch_grammar(grammar: str):
available_grammars = {
"getkw": getkw.grammar(),
"standard": getkw.grammar(has_complex=True),
}
return available_grammars[grammar]
Loading

0 comments on commit c01b439

Please sign in to comment.