ATTENTION: We're still in a very early alpha version, the API may and will change frequently. Please, use it at your own risk, until we release version 1.0.
Maven:
<dependency>
<groupId>io.github.dgroup</groupId>
<artifactId>tagyml</artifactId>
</dependency>
Gradle:
dependencies {
compile 'io.github.dgroup:tagyml:<version>'
}
Represents the YAML file as an object Teams
The examples below are built on top of snakeyaml framework, but you can override it by using the extension point in favour of your favourite framework.
-
Define YAML file for version
1.0
.version: 1.0 teams: - id: 100 name: BSS dream team lead: name: Bob Marley email: bob.marley@dreamteam.com properties: total-devs: 5 total-managers: 10 - id: 200 name: OSS dream team lead: name: Jules Verne email: jules.verne@dreamteam.com properties: total-devs: 5 total-managers: 5
in
tagyml $ tree ... |-- src | |-- main | | |-- ... | | | `-- test | |-- java | | `-- ... | `-- resources | `-- yaml | |-- teams.yaml | |-- ... ...
-
Define the interfaces for the domain model
/** * YAML file-based team. */ public interface Team { int id() throws YamlFormatException; String name() throws YamlFormatException; User lead() throws YamlFormatException; Properties properties() throws YamlFormatException; } /** * YAML file-based user. */ public interface User { String name(); String email(); }
See UncheckedYamlFormatException in case if you don't wan't to throw the checked exception during YAML file parsing procedure.
-
Define the YAML parsing process
public class Teams implements Scalar<Iterable<Team>> { private static final Logger LOG = LoggerFactory.getLogger(Teams.class); /** * @param path The path to the YAML file. * @param charset The charset of YAML file to read. */ public Teams(final Path path, final Charset charset) { this.path = path; this.charset = charset; } @Override public Iterable<Team> value() throws YamlFormatException { // Parse YAML file using first supported YAML format return new FirstIn<>( // Print to logs the details about failed attempt of // parsing YAML file using a particular format. (version, exception) -> LOG.warn( new FormattedText( "Unable to parse '%s' using '%s' format", this.path, version ).asString(), exception ), // The exception message in case if YAML file can't be // parsed using any specified formats below new FormattedText( "The file %s has unsupported YAML format", this.path ), // The supported YAML formats new TeamsV1(this.path, this.charset), new TeamsV2(this.path, this.charset), ... new TeamsVn(this.path, this.charset) ).value(); } }
See FirstIn.
-
Define YAML format for version 1.0 -
TeamsV1
final class TeamsV1 implements Format<Iterable<Team>> { private final Path path; private final Charset charset; TeamsV1(final Path path, final Charset charset) { this.path = path; this.charset = charset; } @Override public String version() { return "1.0"; } @Override public Iterable<Team> value() throws YamlFormatException { return new Snakeyaml(YamlFile.class) .apply(new YamlText(this.path, this.charset)) .tagTeams(); } /** * The object which represents YAML file. * The YAML file should have at least `version` and collection of `team`. * The EO concept is against getters/setters, but we need them in order to * parse the file by snakeyaml lib, thus, once we completed the parsing * procedure, we should restrict any possible access to such type of objects. */ private static class YamlFile { public double version; public List<YmlTeam> teams; public double tagVersion() throws YamlFormatException { return new Version(this.version).value(); } public Iterable<Team> tagTeams() throws YamlFormatException { return new Mapped<>( YamlTeam::tagTeam, new TagOf<>("teams", this.teams).value() ); } } /** * Represents 1 element from YAML tag `teams`. */ private static class YmlTeam { public int id; public String name; public YmlTeamMember lead; public Map<String, String> properties; public Team tagTeam() { return new Team() { @Override public int id() throws YamlFormatException { return new TagOf<>("id", id).value(); } @Override public String name() throws YamlFormatException { return new TagOf<>("name", name).value(); } @Override public User lead() throws YamlFormatException { return new TagOf<>("lead", lead).value().tagUser(); } @Override public Properties properties() throws YamlFormatException { return new PropertiesOf("properties", properties).value(); } }; } } /** * Represents YAML tag `lead` */ private static class YmlTeamMember { public String name; public String email; public User tagUser() { return new User() { @Override public String name() { return new Unchecked(new TagOf<>("name", name)).value(); } @Override public String email() { return new Unchecked(new TagOf<>("email", email)).value(); } ); } } }
See Format, Snakeyaml, Version, PropertiesOf, TagOf and Unchecked.
Instead of Snakeyaml you may implement Yaml using your favourite framework in the same way.
-
Parse YAML file in EO way
@Test public void yamlParsingProcessIsSuccessfull() throws YmlFormatException { final Iterable<Team> teams = new Teams( Paths.get("src", "test", "resources", "yml", "1.0", "teams.yml") ).value(); MatcherAssert.assertThat( "There are 2 teams defined in YAML file", teams, Matchers.iterableWithSize(2) ); MatcherAssert.assertThat( "The 1st team defined in YAML tag has id 100", teams.iterator().next().id(), new IsEqual<>(100) ); MatcherAssert.assertThat( "The 1st team defined in YAML tag has property `total-dev`", teams.iterator().next().properties().getProperty("total-dev"), new IsEqual<>(5) ); }
See SnakeyamlTest for details. Of course, the test above is testing a lot of things and should be separated into bunch of tests with single
MatcherAssert.assertThat
each, but here it was done for educational purposes.