Skip to content

Commit

Permalink
#18
Browse files Browse the repository at this point in the history
 Add: file utils, common utils for use in migration logic by users
 Working example for using utils to write a migration
 Update docs to mention utils
  • Loading branch information
cpointe-ibllanos committed Jun 27, 2024
1 parent 308a724 commit 4462eea
Show file tree
Hide file tree
Showing 13 changed files with 471 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public class FooToBarMigration extends AbstractMigration {
}
```

#### Leveraging utilities classes
The following Java classes in `org.technologybrewery.baton.util` can be leveraged to easily implement common migration logic into your extension:
* `CommonUtils`
* `FileUtils`

### Configure Baton to use the migration
With a migration to apply, we both configure and tailor that use through a simple json file. This file can live anywhere
in Baton's classpath and is named `migrations.json` by default.
Expand Down
5 changes: 5 additions & 0 deletions baton-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<artifactId>slf4j-api</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>file-management</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.technologybrewery.baton.util;
import com.vdurmont.semver4j.Semver;

public class CommonUtils {
/**
* Checks if version1 is less than version2 using the Semver4j library.
*
* @param version1
* @param version2
* @return isLessThanVersion - if true, version1 is less than version2.
*/
public static boolean isLessThanVersion(String version1, String version2) {
Semver sem = new Semver(version1);
return sem.isLowerThan(version2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package org.technologybrewery.baton.util;

import org.apache.commons.io.IOUtils;
import org.codehaus.plexus.util.StringUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class FileUtils {

/**
*
* @param file the File
* @param toReplace a string representing the text to replace
* @param replacement the replacement text to substitute the toReplace string
* @return a boolean set to true if at least one replacement was performed in the file
*/
public static boolean replaceLiteralInFile(File file, String toReplace, String replacement) throws IOException {
boolean replacedInFile = false;

if (file != null && file.exists()) {
String content = new String(Files.readAllBytes((file.toPath())));
content = content.replace(toReplace, replacement);
Files.write(file.toPath(), content.getBytes());
replacedInFile = true;
}
return replacedInFile;
}

/**
*
* @param file the File
* @param regex a regex representing the text to replace, as a String
* @param replacement the replacement text to substitute the regex
* @return a boolean set to true if at least one replacement was performed in the file
*/
public static boolean replaceInFile(File file, String regex, String replacement) throws IOException {
boolean replacedInFile = false;
if (file != null && file.exists()) {
Charset charset = StandardCharsets.UTF_8;
String fileContent = new String(Files.readAllBytes(file.toPath()), charset);

Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(fileContent);
String newFileContent = matcher.replaceAll(replacement);
IOUtils.write(newFileContent, new FileOutputStream(file), charset);
replacedInFile = true;
}
return replacedInFile;
}

/**
* Evaluates a file against a regex pattern and replaces a substring of each regex match
* with a specified replacement
* @param file the File
* @param regex a regex representing the text to replace a substring of
* @param substring the substring of the regex match that will be replaced
* @param replacement the replacement of the match substring
* @return a boolean set to true if at least one modification was performed in the file
*/
public static boolean modifyRegexMatchInFile(File file, String regex, String substring, String replacement) {
if (file == null || !file.exists()) {
return false;
}

boolean modified = false;

try {
Path path = file.toPath();
Charset charset = StandardCharsets.UTF_8;
List<String> resultLines = new ArrayList<>();

Pattern pattern = Pattern.compile(regex);
for (String line : Files.readAllLines(path, charset)) {
if (pattern.matcher(line).find()) {
line = line.replace(substring, replacement);
modified = true;
}
resultLines.add(line);
}
if (modified) {
Files.write(path, resultLines, charset);
}
} catch (IOException e) {
return false;
}
return modified;
}

/**
* Reads in the {@link File} object and returns a {@link List} of the contents.
* @param file {@link File} to read
* @return {@link List} of the contents
* @throws IOException
*/
public static List<String> readAllFileLines(File file) throws IOException {
return Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
}

/**
* Writes a {@link List} of the contents to the {@link File} object.
* @param file {@link File} to write
* @param contents {@link List} of the contents
* @throws IOException
*/
public static void writeFile(File file, List<String> contents) throws IOException {
Files.write(file.toPath(), contents, StandardCharsets.UTF_8);
}

/**
* @see FileUtils#getRegExCaptureGroups(String, String)
* @param regex a regex containing capture groups, as a String
* @param file the file to search for matching capture groups
* @return An ArrayList of Strings representing each capture group in the regex that was matched
*/
public static ArrayList<String> getRegExCaptureGroups(String regex, File file) throws IOException {
String fileContent = "";
if (file != null && file.exists()) {
Charset charset = StandardCharsets.UTF_8;
fileContent = new String(Files.readAllBytes(file.toPath()), charset);
}
return StringUtils.isNotEmpty(fileContent) ? getRegExCaptureGroups(regex, fileContent) : new ArrayList<>();
}

/**
*
* @param regex a regex containing capture groups, as a String
* @param input the string to search for matching capture groups
* @return An ArrayList of Strings representing each capture group in the regex that was matched
*/
public static ArrayList<String> getRegExCaptureGroups(String regex, String input) {
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(input);

ArrayList<String> captured = new ArrayList<>();
if (matcher.find()) {
// Skip the 0 index -- the first match is always all capture groups put together
for (int i = 1; i <= matcher.groupCount(); ++i) {
captured.add(matcher.group(i));
}
}

return captured;
}

/**
* Evaluates a regex pattern against a file to determine if at least one regex match exists
*
* @param regex a regex pattern, as a String
* @param file the file to search for matching substrings
* @return true if there is at least one regex match, otherwise false
*/
public static boolean hasRegExMatch(String regex, File file) throws IOException {
String fileContent;
if (file != null && file.exists()) {
Charset charset = StandardCharsets.UTF_8;
fileContent = Files.readString(file.toPath(), charset);
return Pattern.compile(regex, Pattern.MULTILINE).matcher(fileContent).find();
} else {
return false;
}
}

/**
* Infers the indentation style from the given line.
*
* @param line the line to infer the indentation style from
* @param level the level of indentation of the line
* @return a single indent in the inferred style
*/
public static String getIndent(String line, int level) {
if( level < 1 ) {
return "";
}
int i = 0;
while (i < line.length() && Character.isWhitespace(line.charAt(i))) {
i++;
}
return line.substring(0, i/level);
}

/**
* Indent the values the desired number of tabs with a variable tab size.
* @param values List of {@link String} values to indent
* @param numSpaces number of spaces to indent
*/
public static void indentValues(List<String> values, int numSpaces) {
String SPACE = " ";
for (int i = 0; i < values.size(); i++) {
if (!values.get(i).isBlank()) {
values.set(i, SPACE.repeat(numSpaces) + values.get(i));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.technologybrewery.baton;

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.technologybrewery.baton.util.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;


public class UtilsTestSteps {

private static final File DEFAULT_TEST_DIRECTORY = new File("target/utils-test/");
private static final String DEFAULT_TEST_FILE_NAME = "text_file.txt";
File testFile;
Boolean match;
String inputAsString;
List<String> captureGroups;

@Given("I have a string containing {string}")
public void i_have_a_string_containing(String str) throws Throwable {
inputAsString = str;
}

@When("I use the regex {string} to retrieve capture groups")
public void i_use_the_regex_to_retrieve_capture_groups(String regex) throws IOException {
if (testFile != null) {
captureGroups = FileUtils.getRegExCaptureGroups(regex, testFile);
} else {
captureGroups = FileUtils.getRegExCaptureGroups(regex, inputAsString);
}
}

@Then("the size of the retrieved capture groups should be \"{int}\"")
public void the_size_of_the_retrieved_capture_groups_should_be(int groups) {
assertEquals(groups, captureGroups.size(),"Unexpected size for capture groups");
}

@Then("the capture groups should include")
public void the_capture_groups_should_include(List<String> expectedGroups) {
assertEquals(expectedGroups, captureGroups);
}

@Given("I have a file containing the string {string}")
public void i_have_a_file_containing_the_string(String string) throws IOException {
testFile = new File(DEFAULT_TEST_DIRECTORY, DEFAULT_TEST_FILE_NAME);
if (DEFAULT_TEST_DIRECTORY.mkdirs()) {
FileUtils.writeFile(testFile, Collections.singletonList(string));
}
}

@When("I use the regex {string} to substitute {string}")
public void i_use_the_regex_to_substitute(String regex, String substitute) throws IOException {
FileUtils.replaceInFile(testFile, regex, substitute);
}

@When("I use the literal {string} to substitute {string}")
public void iUseTheLiteralToSubstitute(String literal, String substitute) throws IOException {
FileUtils.replaceLiteralInFile(testFile, literal, substitute);
}

@Then("the file should now contain the string {string}")
public void the_file_should_now_contain_the_string(String string) throws IOException {
assertTrue(FileUtils.hasRegExMatch(string, testFile), String.format("File does not contain expected string %s",string));
}

@When("I use the regex {string} to substitute {string} with {string}")
public void i_use_the_regex_to_substitute_with(String regex, String target, String substitute) {
FileUtils.modifyRegexMatchInFile(testFile, regex, target, substitute);
}

@When("I use the regex {string} to search for matches in a file")
public void i_use_the_regex_to_search_for_matches_in_a_file(String regex) throws IOException {
match = FileUtils.hasRegExMatch(regex, testFile);

}

@Then("the match result should be \"{booleanValue}\"")
public void the_match_result_should_be(Boolean expected) {
assertEquals(expected, match, "RegEx file matcher did not return expected result");

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

package org.technologybrewery.baton.util;

import io.cucumber.java.ParameterType;

public class TestUtils {
@ParameterType( value = "true|True|TRUE|false|False|FALSE")
public Boolean booleanValue(String value){
return Boolean.valueOf(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Feature: Baton utilities can be used to assist with commonly used logic in a migration
Scenario: I can easily compare the semantic versioning of two strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@baton
Feature: Baton utilities can be used to assist with migration logic pertaining to files

Scenario: I want to easily replace a string in a file using a literal expression
Given I have a file containing the string "123abc456"
When I use the literal "abc" to substitute "def"
Then the file should now contain the string "123def456"

Scenario: I want to easily replace a string in a file using a regex expression
Given I have a file containing the string "123abc456"
When I use the regex "(?<=123).*(?=456)" to substitute "def"
Then the file should now contain the string "123def456"

Scenario: I want to easily replace a substring of a string in a file using a regex expression
Given I have a file containing the string "123abc456"
When I use the regex "123.*456" to substitute "abc" with "def"
Then the file should now contain the string "123def456"

Scenario: I want to easily test if a string is present in a file using a regex expression
Given I have a file containing the string "123abc456"
When I use the regex "123.*456" to search for matches in a file
Then the match result should be "true"

Scenario: I want to easily be able to pull out specific substrings from a string using regex capture groups
Given I have a string containing "123abc456"
When I use the regex "123(.*)456" to retrieve capture groups
Then the size of the retrieved capture groups should be "1"
And the capture groups should include
| abc |

Scenario: I want to easily be able to pull out specific strings from a file using regex capture groups
Given I have a file containing the string "123abc456"
When I use the regex "(123).*(456)" to retrieve capture groups
Then the size of the retrieved capture groups should be "2"
And the capture groups should include
| 123 |
| 456 |

Scenario: I want to easily retrieve the type of indexing being used in a string

Scenario: I want to easily indent a group of strings with spaces

Scenario: I want to easily retrieve the contents of a file

Scenario: I want to easily write to a file
Loading

0 comments on commit 4462eea

Please sign in to comment.