Skip to content

Commit

Permalink
Merge pull request #43 from wwkimball/development
Browse files Browse the repository at this point in the history
Release 2.3.0
  • Loading branch information
wwkimball authored Sep 22, 2019
2 parents d95b06d + 4e36587 commit a00e11d
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 35 deletions.
9 changes: 9 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
2.3.0:
Bug Fixes:
* The get_yaml_data helper function now contains ruamel.yaml errors/warnings
without disrupting calling context handlers.

Enhancements:
* yaml-paths version 0.2.0 now has more detailed output control, trading
--pathonly for --values, --nofile, --noexpression, and --noyamlpath.

2.2.0:
Bug Fixes:
* YAML construction errors are now caught and more cleanly reported by all
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="yamlpath",
version="2.2.0",
version="2.3.0",
description="Read and change YAML/Compatible data using powerful, intuitive, command-line friendly syntax",
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down
26 changes: 24 additions & 2 deletions tests/test_commands_yaml_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ def test_multi_file_multi_expression_pathonly_search(self, script_runner, tmp_pa
yaml_file2 = create_temp_yaml_file(tmp_path_factory, content2)
result = script_runner.run(
self.command,
"--pathsep=/", "--keynames", "--pathonly",
"--pathsep=/", "--keynames", "--noexpression",
"--search", "^value",
"--search", "=bravo",
yaml_file1, yaml_file2
Expand Down Expand Up @@ -571,7 +571,7 @@ def test_dedupe_results(self, script_runner, tmp_path_factory):
yaml_file = create_temp_yaml_file(tmp_path_factory, content)
result = script_runner.run(
self.command,
"--pathsep=/", "--keynames", "--pathonly",
"--pathsep=/", "--keynames", "--noexpression",
"--search", "=key",
"--search", "=value",
yaml_file
Expand Down Expand Up @@ -882,3 +882,25 @@ def test_yield_raw_children_direct(self, tmp_path_factory, quiet_logger):
include_key_aliases=False, include_value_aliases=False
)):
assert assertion == str(path)

def test_value_dump(self, script_runner, tmp_path_factory):
content = """---
sample_scalar: value
sample_hash:
sub:
- list
- elements
"""
yaml_file = create_temp_yaml_file(tmp_path_factory, content)
result = script_runner.run(
self.command,
"--pathsep=/",
"--keynames", "--values",
"--search", "^sample",
yaml_file
)
assert result.success, result.stderr
assert "\n".join([
"/sample_scalar: value",
'/sample_hash: {"sub": ["list", "elements"]}',
]) + "\n" == result.stdout
87 changes: 63 additions & 24 deletions yamlpath/commands/yaml_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
Copyright 2019 William W. Kimball, Jr. MBA MSIS
"""
import argparse
import json
from os import access, R_OK
from os.path import isfile
from typing import Any, Generator, List, Optional, Tuple
Expand Down Expand Up @@ -33,7 +34,7 @@
from yamlpath.eyaml import EYAMLProcessor

# Implied Constants
MY_VERSION = "0.1.0"
MY_VERSION = "0.2.0"

def processcli():
"""Process command-line arguments."""
Expand Down Expand Up @@ -78,16 +79,34 @@ def processcli():
action="store_true",
help="suppress all non-result output except errors")

parser.add_argument(
"-p", "--pathonly",
action="store_true",
help="print results without any search expression decorators")

parser.add_argument(
"-m", "--expand",
action="store_true",
help="expand matching parent nodes to list all permissible child leaf\
nodes (see \"Reference handling options\" for restrictions)")
nodes (see \"reference handling options\" for restrictions)")

valdump_group = parser.add_argument_group("result printing options")
valdump_group.add_argument(
"-L", "--values",
action="store_true",
help="print the values or elements along with each YAML Path (complex\
results are emitted as JSON; use --expand to emit only simple\
values)")
valdump_group.add_argument(
"-F", "--nofile",
action="store_true",
help="omit source file path and name decorators from the output\
(applies only when searching multiple files)")
valdump_group.add_argument(
"-X", "--noexpression",
action="store_true",
help="omit search expression decorators from the output")
valdump_group.add_argument(
"-P", "--noyamlpath",
action="store_true",
help="omit YAML Paths from the output (useful with --values or to\
indicate whether a file has any matches without printing them\
all, perhaps especially with --noexpression)")

parser.add_argument(
"-t", "--pathsep",
Expand All @@ -98,7 +117,7 @@ def processcli():
help="indicate which YAML Path seperator to use when rendering\
results; default=dot")

keyname_group_ex = parser.add_argument_group("Key name searching options")
keyname_group_ex = parser.add_argument_group("key name searching options")
keyname_group = keyname_group_ex.add_mutually_exclusive_group()
keyname_group.add_argument(
"-i", "--ignorekeynames",
Expand All @@ -119,7 +138,7 @@ def processcli():
help="also search the names of &anchor and *alias references")

dedup_group_ex = parser.add_argument_group(
"Reference handling options",
"reference handling options",
"Indicate how to treat anchor and alias references. An anchor is an\
original, reusable key or value. All aliases become replaced by the\
anchors they reference when YAML data is read. These options specify\
Expand Down Expand Up @@ -536,7 +555,7 @@ def search_for_paths(logger: ConsolePrinter, processor: EYAMLProcessor,
include_value_aliases=include_value_aliases):
yield path
else:
yield tmp_path
yield YAMLPath(tmp_path)
continue

if isinstance(val, (CommentedSeq, CommentedMap)):
Expand Down Expand Up @@ -617,28 +636,48 @@ def get_search_term(logger: ConsolePrinter,

return exterm

def print_results(args: Any, yaml_file: str,
def print_results(args: Any, processor: EYAMLProcessor, yaml_file: str,
yaml_paths: List[Tuple[str, YAMLPath]]) -> None:
"""
Dumps the search results to STDOUT with optional and dynamic formatting.
"""
in_file_count = len(args.yaml_files)
in_expressions = len(args.search)
suppress_expression = in_expressions < 2 or args.pathonly
print_file_path = in_file_count > 1 and not args.nofile
print_expression = in_expressions > 1 and not args.noexpression
print_yaml_path = not args.noyamlpath
print_value = args.values
buffers = [
": " if print_file_path or print_expression and (
print_yaml_path or print_value
) else "",
": " if print_yaml_path and print_value else "",
]
for entry in yaml_paths:
expression, result = entry
resline = ""
if in_file_count > 1:
if suppress_expression:
resline += "{}: {}".format(yaml_file, result)
else:
resline += "{}[{}]: {}".format(
yaml_file, expression, result)
else:
if suppress_expression:
resline += "{}".format(result)
else:
resline += "[{}]: {}".format(expression, result)

if print_file_path:
resline += "{}".format(yaml_file)

if print_expression:
resline += "[{}]".format(expression)

resline += buffers[0]
if print_yaml_path:
resline += "{}".format(result)

resline += buffers[1]
if print_value:
# These results can have only one match, but make sure lest the
# output become messy.
for node in processor.get_nodes(result, mustexist=True):
if isinstance(node, (dict, list)):
resline += "{}".format(json.dumps(node))
else:
resline += "{}".format(str(node).replace("\n", r"\n"))
break

print(resline)

def main():
Expand Down Expand Up @@ -739,7 +778,7 @@ def main():
yaml_paths.remove(entry)
break # Entries are already unique

print_results(args, yaml_file, yaml_paths)
print_results(args, processor, yaml_file, yaml_paths)

exit(exit_state)

Expand Down
20 changes: 13 additions & 7 deletions yamlpath/func.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ def get_yaml_editor() -> Any:
Raises: N/A
"""
# The ruamel.yaml class appears to be missing some typing data, so these
# valid assignments cannot be type-checked.
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
yaml.explicit_start = True
yaml.preserve_quotes = True
yaml.width = maxsize
yaml.explicit_start = True # type: ignore
yaml.preserve_quotes = True # type: ignore
yaml.width = maxsize # type: ignore
return yaml

# pylint: disable=locally-disabled,too-many-branches
# pylint: disable=locally-disabled,too-many-branches,too-many-statements
def get_yaml_data(parser: Any, logger: ConsolePrinter, source: str) -> Any:
"""
Attempts to parse YAML/Compatible data and return the ruamel.yaml object
Expand All @@ -64,13 +66,17 @@ def get_yaml_data(parser: Any, logger: ConsolePrinter, source: str) -> Any:
the data could not be loaded.
"""
import warnings
warnings.filterwarnings("error")
yaml_data = None

# Try to open the file
# This code traps errors and warnings from ruamel.yaml, substituting
# lengthy stack-dumps with specific, meaningful feedback. Further, some
# warnings are treated as errors by ruamel.yaml, so these are also
# coallesced into cleaner feedback.
try:
with open(source, 'r') as fhnd:
yaml_data = parser.load(fhnd)
with warnings.catch_warnings():
warnings.filterwarnings("error")
yaml_data = parser.load(fhnd)
except KeyboardInterrupt:
logger.error("Aborting data load due to keyboard interrupt!")
yaml_data = None
Expand Down
4 changes: 3 additions & 1 deletion yamlpath/patches/emitter_write_folded_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,6 @@ def write_folded_fix(self, text):
end += 1


Emitter.write_folded = write_folded_fix
# MYPY hates MonkeyPatching per https://github.com/python/mypy/issues/2427
# but there's no choice here, so... ignore the type.
Emitter.write_folded = write_folded_fix # type: ignore

0 comments on commit a00e11d

Please sign in to comment.