Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring Yaml parser/dumper #70

Merged
merged 7 commits into from
Oct 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import io.github.alechenninger.monarch.apply.ApplyChangesInput;
import io.github.alechenninger.monarch.set.UpdateSetInput;
import io.github.alechenninger.monarch.yaml.YamlConfiguration;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Argument;
Expand All @@ -34,6 +35,7 @@

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -224,7 +226,7 @@ interface InputFactory<T> {
T getInput(Namespace parsed);
}

static CommandSpec<ApplyChangesInput> applySpec = new CommandSpec<ApplyChangesInput>() {
private static final CommandSpec<ApplyChangesInput> applySpec = new CommandSpec<ApplyChangesInput>() {
@Override
public String name() {
return "apply";
Expand Down Expand Up @@ -290,7 +292,9 @@ public InputFactory<ApplyChangesInput> addToSubparsers(Subparsers subparsers) {
.nargs("+")
.help("Space delimited paths to files which configures default values for command line "
+ "options. The default config path of ~/.monarch/config.yaml is always checked. " +
"Config values read are 'dataDir', 'outputDir', and 'hierarchy'.");
"Config values read are 'dataDir', 'outputDir', 'hierarchy', and 'dataFormats'. " +
"'dataFormats' has sub values for supported data formats, like 'yaml'. Each data " +
"format has its own options. 'yaml' has 'indent' and 'isolate'.");

subparser.addArgument("--hierarchy", "-h")
.dest("hierarchy")
Expand Down Expand Up @@ -344,6 +348,20 @@ public InputFactory<ApplyChangesInput> addToSubparsers(Subparsers subparsers) {
+ "either collections or maps. If not provided, will look for an array value in "
+ "config files with key 'outputDir'.");

subparser.addArgument("--yaml-isolate")
.dest("yaml_isolate")
.choices(Arrays.stream(YamlConfiguration.Isolate.values())
.map(i -> i.toString().toLowerCase())
.collect(Collectors.toList()))
.help("Controls when you want monarch to possibly avoid destructive edits to existing " +
"YAML data sources with regards to format, ordering, and/or comments outside of " +
"keys previously created by monarch. Always will cause monarch to abort updating a " +
"source that would require changing keys not previously managed by monarch. Never " +
"will cause monarch to always manage the entire source.\n" +
"\n" +
"Defaults to config files (see --config), and if neither are set defaults to '" +
YamlConfiguration.DEFAULT.updateIsolation().name().toLowerCase() + "'.");

return parsed -> new ApplyChangesInput() {
@Override
public Optional<String> getHierarchyPathOrYaml() {
Expand Down Expand Up @@ -398,11 +416,18 @@ public boolean isHelpRequested() {
public String getHelpMessage() {
return subparser.formatHelp();
}

@Override
public Optional<YamlConfiguration.Isolate> getYamlIsolate() {
return Optional.ofNullable(parsed.getString("yaml_isolate"))
.map(String::toUpperCase)
.map(YamlConfiguration.Isolate::valueOf);
}
};
}
};

static CommandSpec<UpdateSetInput> updateSetSpec = new CommandSpec<UpdateSetInput>() {
private static final CommandSpec<UpdateSetInput> updateSetSpec = new CommandSpec<UpdateSetInput>() {
@Override
public String name() {
return "set";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,13 @@

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

/**
* Parses some kind(s) of {@link InputStream} into monarch primitives like {@link Hierarchy} and
* {@link Change}.
*
* <p>For example, the {@link YamlMonarchParser} can parse YAML files. Other parsers capable of
* parsing other kinds of input streams may exist.
* Abstracts a data format that can be used for defining hierarchies, changes, and data sources.
*/
public interface MonarchParser {
public interface DataFormat {
Hierarchy parseHierarchy(InputStream hierarchyInput);
List<Change> parseChanges(InputStream changesInput);
Map<String, Object> parseMap(InputStream inputStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

package io.github.alechenninger.monarch;

import io.github.alechenninger.monarch.yaml.YamlConfiguration;
import io.github.alechenninger.monarch.yaml.YamlDataFormat;
import org.yaml.snakeyaml.Yaml;

import java.io.ByteArrayInputStream;
Expand All @@ -31,15 +33,22 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Represents both a collection of known {@link MonarchParser}s by capability, and a strategy for
* Represents both a collection of known {@link DataFormat}s by capability, and a strategy for
* determining which parser to use given a {@link Path} or file extension, etc.
*/
public interface MonarchParsers {
MonarchParser yaml();
public interface DataFormats {
DataFormat yaml();

default MonarchParser forPath(Path path) {
/**
* @return New {@code DataFormats} using the supplied configuration. The current object is not
* reconfigured.
*/
DataFormats withConfiguration(DataFormatsConfiguration config);

default DataFormat forPath(Path path) {
String fileName = path.getFileName().toString();

int extensionIndex = fileName.lastIndexOf('.');
Expand All @@ -52,7 +61,7 @@ default MonarchParser forPath(Path path) {
return forExtension(extension);
}

default MonarchParser forExtension(String extension) {
default DataFormat forExtension(String extension) {
switch (extension.toLowerCase()) {
case "yml":
case "yaml": return yaml();
Expand All @@ -64,7 +73,7 @@ default MonarchParser forExtension(String extension) {
default Hierarchy parseHierarchy(String pathOrParseable, FileSystem fileSystem) {
try {
Path path = fileSystem.getPath(pathOrParseable);
MonarchParser parser = forPath(path);
DataFormat parser = forPath(path);

try {
return parser.parseHierarchy(Files.newInputStream(path));
Expand Down Expand Up @@ -93,7 +102,7 @@ default Hierarchy parseHierarchy(String pathOrParseable, FileSystem fileSystem)
default List<Change> parseChanges(String pathOrParseable, FileSystem fileSystem) {
try {
Path path = fileSystem.getPath(pathOrParseable);
MonarchParser parser = forPath(path);
DataFormat parser = forPath(path);

if (Files.notExists(path)) {
return Collections.emptyList();
Expand Down Expand Up @@ -197,20 +206,37 @@ default Map<String, SourceData> parseDataSourcesInHierarchy(Path dataDir, Hierar
return data;
}

class Default implements MonarchParsers {
class Default implements DataFormats {
private final DataFormatsConfiguration config;
private final Yaml yaml;

public Default() {
this(new Yaml());
}

public Default(DataFormatsConfiguration config) {
this.config = config;
this.yaml = new Yaml();
}

public Default(Yaml yaml) {
this.yaml = yaml;
this.config = new DataFormatsConfiguration() {
@Override
public Optional<YamlConfiguration> yamlConfiguration() {
return Optional.empty();
}
};
}

@Override
public DataFormat yaml() {
return config.yamlConfiguration().map(YamlDataFormat::new).orElse(new YamlDataFormat(yaml));
}

@Override
public MonarchParser yaml() {
return new YamlMonarchParser(yaml);
public DataFormats withConfiguration(DataFormatsConfiguration config) {
return new Default(config);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* monarch - A tool for managing hierarchical data.
* Copyright (C) 2016 Alec Henninger
* Copyright (C) 2016 Alec Henninger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -16,23 +16,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package io.github.alechenninger.monarch
package io.github.alechenninger.monarch;

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.yaml.snakeyaml.Yaml
import io.github.alechenninger.monarch.yaml.YamlConfiguration;

@RunWith(JUnit4.class)
class YamlMonarchParserTest {
def parser = new YamlMonarchParser(new Yaml())
import java.util.Optional;

@Test
void shouldTolerateEmptyYamlDocumentsWhenParseChangeset() {
def changesetInput = new ByteArrayInputStream('\n---\n'.getBytes("UTF-8"))

Iterable<Change> parsed = parser.parseChanges(changesetInput)

assert parsed.iterator().hasNext() == false
}
public interface DataFormatsConfiguration {
Optional<YamlConfiguration> yamlConfiguration();
}
39 changes: 26 additions & 13 deletions bin/src/io/github/alechenninger/monarch/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.github.alechenninger.monarch.apply.ApplyChangesOptions;
import io.github.alechenninger.monarch.set.UpdateSetInput;
import io.github.alechenninger.monarch.set.UpdateSetOptions;
import io.github.alechenninger.monarch.yaml.YamlConfiguration;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
Expand Down Expand Up @@ -50,7 +51,7 @@ public class Main {
private final Path defaultConfigPath;
private final FileSystem fileSystem;
private final Monarch monarch;
private final MonarchParsers parsers;
private final DataFormats dataFormats;
private final Yaml yaml;
private final PrintStream consoleOut;
// TODO make this configurable; maybe use a 'real' logger
Expand All @@ -59,16 +60,18 @@ public class Main {
private final MonarchArgParser parser;

public Main(Monarch monarch, Yaml yaml, String defaultConfigPath, FileSystem fileSystem,
MonarchParsers parsers, OutputStream consoleOut) {
DataFormats dataFormats, OutputStream consoleOut,
YamlConfiguration.Isolate defaultYamlIsolate) {
this.monarch = monarch;
this.yaml = yaml;
this.parsers = parsers;
this.dataFormats = dataFormats;
this.consoleOut = consoleOut instanceof PrintStream
? (PrintStream) consoleOut
: new PrintStream(consoleOut);
this.defaultConfigPath = fileSystem.getPath(defaultConfigPath);
this.fileSystem = fileSystem;
this.parser = new ArgParseMonarchArgParser(new DefaultAppInfo(), this.consoleOut);
this.parser = new ArgParseMonarchArgParser(new DefaultAppInfo(), this.consoleOut
);
}

public int run(String argsSpaceDelimited) {
Expand Down Expand Up @@ -103,7 +106,7 @@ public int run(String... args) {

try {
UpdateSetOptions options = UpdateSetOptions.fromInputAndConfigFiles(updateSetInput,
fileSystem, parsers, defaultConfigPath);
fileSystem, dataFormats, defaultConfigPath);

SourceSpec source = options.source()
.orElseThrow(missingOptionException("source"));
Expand All @@ -128,7 +131,11 @@ public int run(String... args) {

try {
ApplyChangesOptions options = ApplyChangesOptions.fromInputAndConfigFiles(
applyChangesInput, fileSystem, parsers, defaultConfigPath);
applyChangesInput, fileSystem, dataFormats, defaultConfigPath);

DataFormats configuredFormats = options.dataFormatsConfiguration()
.map(dataFormats::withConfiguration)
.orElse(dataFormats);

Path outputDir = options.outputDir()
.orElseThrow(missingOptionException("output directory"));
Expand All @@ -140,7 +147,7 @@ public int run(String... args) {
.orElseThrow(missingOptionException("target"));

Map<String, SourceData> currentData =
parsers.parseDataSourcesInHierarchy(dataDir, hierarchy);
configuredFormats.parseDataSourcesInHierarchy(dataDir, hierarchy);
Source target = hierarchy.sourceFor(targetSpec).orElseThrow(
() -> new IllegalArgumentException("Target source not found in hierarchy: " + targetSpec));

Expand All @@ -159,7 +166,8 @@ public int run(String... args) {
private void applyChanges(Path outputDir, Iterable<Change> changes, Set<String> mergeKeys,
Map<String, SourceData> currentSources, Source target) throws IOException {
if (!changes.iterator().hasNext()) {
consoleOut.println("No changes provided; formatting target.");
consoleOut.println("No changes provided. Still writing sources at and under target to " +
outputDir);
}

List<String> affectedSources = target.descendants().stream()
Expand All @@ -184,15 +192,15 @@ private void applyChanges(Path outputDir, Iterable<Change> changes, Set<String>
Map<String, Object> outData = pathToData.getValue();
SourceData sourceData = currentSources.containsKey(path)
? currentSources.get(path)
: parsers.forPath(outPath).newSourceData();
: dataFormats.forPath(outPath).newSourceData();

if (sourceData.isEmpty() && outData.isEmpty()) {
continue;
}

try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
sourceData.writeNew(outData, out);
sourceData.writeUpdate(outData, out);
ensureParentDirectories(outPath);
Files.write(outPath, out.toByteArray());
} catch (Exception e) {
Expand Down Expand Up @@ -283,7 +291,7 @@ private void printError(Throwable e) {
public static void main(String[] args) throws IOException, ArgumentParserException {
DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setPrettyFlow(true);
dumperOptions.setIndent(2);
dumperOptions.setIndent(YamlConfiguration.DEFAULT.indent());
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);

Yaml yaml = new Yaml(dumperOptions);
Expand All @@ -293,8 +301,13 @@ public static void main(String[] args) throws IOException, ArgumentParserExcepti
yaml,
System.getProperty("user.home") + "/.monarch/config.yaml",
FileSystems.getDefault(),
new MonarchParsers.Default(yaml),
System.out)
new DataFormats.Default(new DataFormatsConfiguration() {
@Override
public Optional<YamlConfiguration> yamlConfiguration() {
return Optional.of(YamlConfiguration.DEFAULT);
}
}),
System.out, YamlConfiguration.DEFAULT.updateIsolation())
.run(args);

System.exit(exitCode);
Expand Down
Loading