Skip to content

Commit

Permalink
add --props-file option
Browse files Browse the repository at this point in the history
Signed-off-by: aserkes <andrii.serkes@oracle.com>
  • Loading branch information
aserkes committed Jan 10, 2023
1 parent 08b55d2 commit 31af28e
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,7 +40,7 @@ private static boolean isLastChild(ContextNode node) {
}

private static String printValue(ContextValue value) {
return value.value().unwrap() + " (" + value.kind() + ')';
return value == null ? "null" : value.value().unwrap() + " (" + value.kind() + ')';
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.archetype.engine.v2.context;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* Context serializer.
*/
public class ContextSerializer implements ContextEdge.Visitor {

private static final Set<ContextValue.ValueKind> DEFAULT_VALUE_KIND_FILTER =
Set.of(ContextValue.ValueKind.EXTERNAL, ContextValue.ValueKind.USER);
private static final Predicate<ContextEdge> DEFAULT_CONTEXT_FILTER =
edge ->
edge.value() != null
&& !edge.node().visibility().equals(ContextScope.Visibility.UNSET)
&& DEFAULT_VALUE_KIND_FILTER.contains(edge.value().kind());
private static final Function<String, String> DEFAULT_VALUE_MAPPER = value -> {
Set<Character> forRemoval = Set.of('[', ']');
if (value == null || value.length() == 0) {
return value;
}
StringBuilder builder = new StringBuilder(value);
if (forRemoval.contains(builder.charAt(0))) {
builder.deleteCharAt(0);
}
if (builder.length() > 0 && forRemoval.contains(builder.charAt(builder.length() - 1))) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
};
private static final String DEFAULT_VALUE_DELIMITER = ",";

private final Map<String, String> result;
private final Predicate<ContextEdge> filter;
private final CharSequence valueDelimiter;
private final Function<String, String> valueMapper;

private ContextSerializer(Map<String, String> result,
Predicate<ContextEdge> filter,
Function<String, String> valueMapper,
CharSequence valueDelimiter) {
this.result = result;
this.filter = filter;
this.valueMapper = valueMapper;
this.valueDelimiter = valueDelimiter;
}

@Override
public void visit(ContextEdge edge) {
ContextNode node = edge.node();
ContextNode parent = node.parent0();
if (parent != null) {
String key = node.path();
Set<String> valueSet = edge.variations().stream()
.filter(filter)
.map(variation -> variation.value().unwrap().toString())
.map(valueMapper)
.collect(Collectors.toSet());
if (valueSet.size() > 0) {
result.put(key, String.join(valueDelimiter, valueSet));
}
}
}

/**
* Visit the given archetype context and return values from the context in form of a map where keys are paths of nodes and
* values are related values for these nodes.
*
* @param context context for processing
* @param filter filter for context values
* @param valueMapper mapper for the values of the nodes
* @param valueDelimiter delimiter for the values
* @return map where keys are paths of nodes and values are related values for these nodes
*/
public static Map<String, String> serialize(Context context,
Predicate<ContextEdge> filter,
Function<String, String> valueMapper,
CharSequence valueDelimiter) {
Map<String, String> result = new HashMap<>();
context.scope().visitEdges(new ContextSerializer(result, filter, valueMapper, valueDelimiter), false);
return result;
}

/**
* Visit the given archetype context and return values from the context that were used by an user during the project
* generation in form of a map where keys are paths of nodes and values are related values for these nodes.
*
* @param context context for processing
* @return map where keys are paths of nodes and values are related values for these nodes
*/
public static Map<String, String> serialize(Context context) {
Map<String, String> result = new HashMap<>();
context.scope()
.visitEdges(new ContextSerializer(result, DEFAULT_CONTEXT_FILTER, DEFAULT_VALUE_MAPPER, DEFAULT_VALUE_DELIMITER),
false);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.archetype.engine.v2.context;

import java.util.Map;
import java.util.function.Function;

import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.isIn;

/**
* Tests {@link ContextSerializer}.
*/
class ContextSerializerTest {

@Test
public void testSerialize() {
Map<String, String> expectedResult = Map.of(
"foo", "foo",
"foo1", "",
"foo2", "some_var_default",
"foo3", "bar1_value"
);
Context context = Context.builder()
.externalDefaults(Map.of(
"some_var", "some_var_default",
"bar1", "bar1_default_value"
))
.externalValues(Map.of(
"foo", "foo",
"bar", "${foo}",
"foo1", "${non_exist_var}",
"foo2", "${some_var}",
"bar1", "bar1_value",
"foo3", "${bar1}"
))
.build();

Map<String, String> result = ContextSerializer.serialize(context,
edge -> edge.node().path().startsWith("foo"),
Function.identity(),
",");

assertThat(result.entrySet(), everyItem(isIn(expectedResult.entrySet())));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,6 +39,8 @@
import io.helidon.build.cli.harness.CommandModel.KeyValueInfo;
import io.helidon.build.cli.harness.CommandModel.KeyValuesInfo;
import io.helidon.build.cli.harness.CommandParameters.ParameterInfo;
import io.helidon.build.common.FileUtils;
import io.helidon.build.common.RequirementFailure;

/**
* Command parser.
Expand Down Expand Up @@ -242,7 +244,16 @@ private Resolver parseCommand(Map<String, ParameterInfo<?>> parametersMap) {
continue;
}
}
parsedParams.put(optionName, new KeyValueParam(optionName, it.next().trim()));
KeyValueParam keyValueParam = new KeyValueParam(optionName, it.next().trim());
parsedParams.put(optionName, keyValueParam);
if (keyValueParam.name().equals(GlobalOptions.PROPS_FILE_OPTION_NAME)) {
try {
Properties props = FileUtils.loadProperties(keyValueParam.value);
props.forEach((key, value) -> properties.put(String.valueOf(key), String.valueOf(value)));
} catch (IOException e) {
throw new RequirementFailure(e.getMessage());
}
}
} else if (paramInfo instanceof KeyValuesInfo) {
boolean required = ((KeyValuesInfo<?>) paramInfo).required();
if (!it.hasNext()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -131,6 +131,37 @@ public class GlobalOptions {
*/
public static final String PLAIN_FLAG_ARGUMENT = "--" + PLAIN_FLAG_NAME;

/**
* The --args-file option name.
*/
public static final String ARGS_FILE_OPTION_NAME = "args-file";

/**
* The --args-file option description.
*/
public static final String ARGS_FILE_OPTION_DESCRIPTION = "Path to a file with arguments for Helidon CLI tool";

/**
* The --args-file option argument.
*/
public static final String ARGS_FILE_OPTION_ARGUMENT = "--" + ARGS_FILE_OPTION_NAME;

/**
* The --props-file option name.
*/
public static final String PROPS_FILE_OPTION_NAME = "props-file";

/**
* The --props-file option description.
*/
public static final String PROPS_FILE_OPTION_DESCRIPTION = "Path to a properties file with user inputs for Helidon init "
+ "command";

/**
* The --props-file option argument.
*/
public static final String PROPS_FILE_OPTION_ARGUMENT = "--" + PROPS_FILE_OPTION_NAME;

/**
* Tests whether the given argument is a global flag.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -53,6 +53,8 @@ private static Map<String, String> createGlobalOptionsMap() {
map.put(GlobalOptions.DEBUG_FLAG_ARGUMENT, GlobalOptions.DEBUG_FLAG_DESCRIPTION);
map.put(GlobalOptions.ERROR_FLAG_ARGUMENT, GlobalOptions.ERROR_FLAG_DESCRIPTION);
map.put(GlobalOptions.PLAIN_FLAG_ARGUMENT, GlobalOptions.PLAIN_FLAG_DESCRIPTION);
map.put(GlobalOptions.ARGS_FILE_OPTION_ARGUMENT, GlobalOptions.ARGS_FILE_OPTION_DESCRIPTION);
map.put(GlobalOptions.PROPS_FILE_OPTION_ARGUMENT, GlobalOptions.PROPS_FILE_OPTION_DESCRIPTION);
return map;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,11 +20,13 @@
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

import io.helidon.build.cli.harness.CommandModel.KeyValueInfo;
import io.helidon.build.cli.harness.CommandModel.FlagInfo;
import io.helidon.build.cli.harness.CommandParser.CommandParserException;
import io.helidon.build.common.RequirementFailure;

import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -305,4 +307,33 @@ public void testArgsFileOptionWithIncorrectFile() {
);
assertThat(e.getMessage(), containsString("java.nio.file.NoSuchFileException: not_existing_file.txt"));
}

@Test
public void testPropsFileOptionWithExistingFile() {
String argsFilePath = Objects.requireNonNull(getClass().getResource("test-props-file.properties")).getPath();
KeyValueInfo<String> propsFileOption = new KeyValueInfo<>(String.class, "props-file", "properties file", null, false);
CommandParameters cmd = new CommandParameters(propsFileOption);

CommandParser parser = CommandParser.create("command", "--props-file", argsFilePath);
CommandParser.Resolver resolver = parser.parseCommand(cmd);

Properties properties = resolver.properties();
assertThat(properties.get("security.atz"), is("abac"));
assertThat(properties.get("flavor"), is("mp"));
assertThat(properties.get("media"), is("json,multipart"));
}

@Test
public void testPropsFileOptionWithIncorrectFile() {
KeyValueInfo<String> propsFileOption = new KeyValueInfo<>(String.class, "props-file", "properties file", null, false);
CommandParameters cmd = new CommandParameters(propsFileOption);

CommandParser parser = CommandParser.create("command", "--props-file", "not_existing_props_file.txt");

RequirementFailure e = assertThrows(
RequirementFailure.class,
() -> parser.parseCommand(cmd)
);
assertThat(e.getMessage(), containsString("not_existing_props_file.txt (No such file or directory)"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Options
--debug Produce debug output
--error Print error stack traces
--plain Do not use color or styles in output
--args-file Path to a file with arguments for Helidon CLI tool
--props-file Path to a properties file with user inputs for Helidon init command

Commands

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Options
--debug Produce debug output
--error Print error stack traces
--plain Do not use color or styles in output
--args-file Path to a file with arguments for Helidon CLI tool
--props-file Path to a properties file with user inputs for Helidon init command
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ Options
--debug Produce debug output
--error Print error stack traces
--plain Do not use color or styles in output
--args-file Path to a file with arguments for Helidon CLI tool
--props-file Path to a properties file with user inputs for Helidon init command
--foo Foo option
--bar Bar option
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#test properties
flavor=mp
security.atz=abac
media=json,multipart
8 changes: 7 additions & 1 deletion cli/impl/etc/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2022 Oracle and/or its affiliates.
Copyright (c) 2022, 2023 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -82,4 +82,10 @@
<Bug pattern="COMMAND_INJECTION"/>
</Match>

<Match>
<!-- reads a file whose location might be specified by user input -->
<Class name="io.helidon.build.cli.impl.InitCommand"/>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>

</FindBugsFilter>
Loading

0 comments on commit 31af28e

Please sign in to comment.