Yaml<->Props conversion commands, tests, integration in VSCode, Eclipse
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
name="Convert .yaml to .properties">
|
||||
</command>
|
||||
</extension>
|
||||
<!--
|
||||
<extension
|
||||
point="org.eclipse.ui.menus">
|
||||
<menuContribution
|
||||
@@ -90,6 +91,7 @@
|
||||
|
||||
</menuContribution>
|
||||
</extension>
|
||||
-->
|
||||
|
||||
<extension point="org.eclipse.ltk.core.refactoring.renameParticipants">
|
||||
<renameParticipant class="org.springframework.ide.eclipse.boot.refactoring.typerename.TypeRenameParticipant" id="org.springframework.ide.eclipse.boot.refactoring.TypeRenameParticipant" name="Rename Companion Type Rename Participant">
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
|
||||
import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
import org.eclipse.ltk.core.refactoring.RefactoringContext;
|
||||
|
||||
public class ConvertPropertiesToYamlHandler extends AbstractHandler {
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 617 B |
@@ -280,6 +280,14 @@
|
||||
class="org.springframework.tooling.boot.ls.commands.AddStartersHandler"
|
||||
commandId="spring.initializr.addStarters">
|
||||
</handler>
|
||||
<handler
|
||||
class="org.springframework.tooling.boot.ls.commands.ConvertBootPropertiesHanlder$ConvertYamlToPropertiesHanlder"
|
||||
commandId="org.springframework.tooling.boot.ls.properties.convert-yaml-to-props">
|
||||
</handler>
|
||||
<handler
|
||||
class="org.springframework.tooling.boot.ls.commands.ConvertBootPropertiesHanlder$ConvertPropertiesToYamlHandler"
|
||||
commandId="org.springframework.tooling.boot.ls.properties.convert-props-to-yaml">
|
||||
</handler>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.commands">
|
||||
@@ -329,6 +337,16 @@
|
||||
typeId="org.eclipse.lsp4e.pathParameterType">
|
||||
</commandParameter>
|
||||
</command>
|
||||
<command
|
||||
description="Converts Spring Boot properties file from .properties format to .yaml format"
|
||||
id="org.springframework.tooling.boot.ls.properties.convert-yaml-to-props"
|
||||
name="Convert .yaml to .properties">
|
||||
</command>
|
||||
<command
|
||||
description="Converts Spring Boot properties file from .yaml format to .properties format"
|
||||
id="org.springframework.tooling.boot.ls.properties.convert-props-to-yaml"
|
||||
name="Convert .properties to .yaml">
|
||||
</command>
|
||||
</extension>
|
||||
|
||||
<extension
|
||||
@@ -765,6 +783,72 @@
|
||||
</and>
|
||||
</visibleWhen>
|
||||
</command>
|
||||
<command
|
||||
commandId="org.springframework.tooling.boot.ls.properties.convert-props-to-yaml"
|
||||
icon="icons/boot-icon.png">
|
||||
<visibleWhen
|
||||
checkEnabled="false">
|
||||
<and>
|
||||
<count
|
||||
value="1">
|
||||
</count>
|
||||
<with
|
||||
variable="activeMenuSelection">
|
||||
<iterate
|
||||
ifEmpty="false">
|
||||
<adapt
|
||||
type="org.eclipse.core.resources.IResource">
|
||||
<and>
|
||||
<test
|
||||
property="org.springsource.ide.eclipse.boot.isBootResource">
|
||||
</test>
|
||||
<test
|
||||
property="org.eclipse.core.resources.extension"
|
||||
value="properties">
|
||||
</test>
|
||||
</and>
|
||||
</adapt>
|
||||
</iterate>
|
||||
</with>
|
||||
</and>
|
||||
</visibleWhen>
|
||||
</command>
|
||||
<command
|
||||
commandId="org.springframework.tooling.boot.ls.properties.convert-yaml-to-props"
|
||||
icon="icons/boot-icon.png">
|
||||
<visibleWhen
|
||||
checkEnabled="false">
|
||||
<and>
|
||||
<count
|
||||
value="1">
|
||||
</count>
|
||||
<with
|
||||
variable="activeMenuSelection">
|
||||
<iterate
|
||||
ifEmpty="false">
|
||||
<adapt
|
||||
type="org.eclipse.core.resources.IResource">
|
||||
<and>
|
||||
<test
|
||||
property="org.springsource.ide.eclipse.boot.isBootResource">
|
||||
</test>
|
||||
<or>
|
||||
<test
|
||||
property="org.eclipse.core.resources.extension"
|
||||
value="yml">
|
||||
</test>
|
||||
<test
|
||||
property="org.eclipse.core.resources.extension"
|
||||
value="yaml">
|
||||
</test>
|
||||
</or>
|
||||
</and>
|
||||
</adapt>
|
||||
</iterate>
|
||||
</with>
|
||||
</and>
|
||||
</visibleWhen>
|
||||
</command>
|
||||
</menuContribution>
|
||||
</extension>
|
||||
<extension
|
||||
|
||||
@@ -46,4 +46,5 @@ public class Constants {
|
||||
public static final String PREF_COMMON_PROPS_METADATA = "boot-java.common.properties-metadata";
|
||||
|
||||
public static final String PREF_MODULITH = "boot-java.modulith-project-tracking";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.tooling.boot.ls.commands;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.commands.AbstractHandler;
|
||||
import org.eclipse.core.commands.ExecutionEvent;
|
||||
import org.eclipse.core.commands.ExecutionException;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IAdaptable;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.lsp4e.LanguageServers;
|
||||
import org.eclipse.lsp4e.LanguageServersRegistry;
|
||||
import org.eclipse.lsp4e.LanguageServersRegistry.LanguageServerDefinition;
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
import org.springframework.tooling.boot.ls.BootLanguageServerPlugin;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public abstract class ConvertBootPropertiesHanlder extends AbstractHandler {
|
||||
|
||||
@Override
|
||||
public Object execute(ExecutionEvent event) throws ExecutionException {
|
||||
IFile sourceFile = getSourceFile(event);
|
||||
IProject project = sourceFile.getProject();
|
||||
if (project != null) {
|
||||
|
||||
// No replace functionality for Eclipse due to issues with undoing the change
|
||||
final boolean replace = false;
|
||||
|
||||
// ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(sourceFile.getFullPath(), LocationKind.IFILE);
|
||||
// if (buffer != null && buffer.isDirty()) {
|
||||
// try {
|
||||
// buffer.commit(null, true);
|
||||
// } catch (CoreException e) {
|
||||
// BootLanguageServerPlugin.getDefault().getLog().log(e.getStatus());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// final boolean replace = BootLanguageServerPlugin.getDefault().getPreferenceStore().getBoolean(Constants.PREF_REPLACE_CONVERTED_PROPS_FILE);
|
||||
//
|
||||
// if (replace) {
|
||||
// // Workaround: LSP4E LSPTextEdit doesn't work well with non opened files in the workspace in this particular case
|
||||
// // Therefore, open the file in an editor thus changes are applied to the document.
|
||||
// try {
|
||||
// IDE.openEditor(UI.getActivePage(), sourceFile);
|
||||
// } catch (PartInitException e) {
|
||||
// BootLanguageServerPlugin.getDefault().getLog().error("", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
IFile targetFile = getTargetFile(sourceFile, getTargetExtension());
|
||||
|
||||
LanguageServerDefinition def = LanguageServersRegistry.getInstance().getDefinition(BootLanguageServerPlugin.BOOT_LS_DEFINITION_ID);
|
||||
Assert.isLegal(def != null, "No definition found for Boot Language Server");
|
||||
|
||||
ExecuteCommandParams commandParams = new ExecuteCommandParams();
|
||||
commandParams.setCommand(getCommandId());
|
||||
commandParams.setArguments(List.of(
|
||||
sourceFile.getLocationURI().toASCIIString(),
|
||||
targetFile.getLocationURI().toASCIIString(),
|
||||
replace
|
||||
));
|
||||
|
||||
LanguageServers.forProject(project).withPreferredServer(def).computeFirst(ls -> ls.getWorkspaceService().executeCommand(commandParams));
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract protected String getTargetExtension();
|
||||
|
||||
abstract protected String getCommandId();
|
||||
|
||||
private IFile getTargetFile(IFile sourceFile, String ext) {
|
||||
IProject project = sourceFile.getProject();
|
||||
IPath eclipsePath = sourceFile.getProjectRelativePath();
|
||||
IPath dir = eclipsePath.removeLastSegments(1);
|
||||
String fileName = sourceFile.getName();
|
||||
String fileNoExt = fileName.substring(0, fileName.length() - sourceFile.getFileExtension().length() - 1);
|
||||
IFile target = project.getFile(dir.append("%s.%s".formatted(fileNoExt, ext)));
|
||||
for (int i = 1; i < Integer.MAX_VALUE && target.exists(); i++) {
|
||||
target = project.getFile(dir.append("%s-%d.%s".formatted(fileNoExt, i, ext)));
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private IFile getSourceFile(ExecutionEvent event) {
|
||||
ISelection selection = HandlerUtil.getActiveMenuSelection(event);
|
||||
IStructuredSelection ss = null;
|
||||
if (selection instanceof IStructuredSelection) {
|
||||
ss = (IStructuredSelection) selection;
|
||||
} else {
|
||||
selection = HandlerUtil.getActiveMenuEditorInput(event);
|
||||
if (selection instanceof IStructuredSelection) {
|
||||
ss = (IStructuredSelection) selection;
|
||||
}
|
||||
}
|
||||
if (ss!=null && !ss.isEmpty()) {
|
||||
return asFile(ss.getFirstElement());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IFile asFile(Object selectedElement) {
|
||||
if (selectedElement instanceof IFile) {
|
||||
return (IFile) selectedElement;
|
||||
}
|
||||
if (selectedElement instanceof IAdaptable) {
|
||||
return ((IAdaptable) selectedElement).getAdapter(IFile.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class ConvertPropertiesToYamlHandler extends ConvertBootPropertiesHanlder {
|
||||
|
||||
@Override
|
||||
protected String getTargetExtension() {
|
||||
return "yml";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCommandId() {
|
||||
return "sts/boot/props-to-yaml";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ConvertYamlToPropertiesHanlder extends ConvertBootPropertiesHanlder {
|
||||
|
||||
@Override
|
||||
protected String getTargetExtension() {
|
||||
return "properties";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCommandId() {
|
||||
return "sts/boot/yaml-to-props";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -61,6 +61,7 @@ public class PrefsInitializer extends AbstractPreferenceInitializer {
|
||||
}));
|
||||
|
||||
preferenceStore.setDefault(Constants.PREF_MODULITH, true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.languageserver.composable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -33,6 +34,7 @@ import org.eclipse.lsp4j.InlayHintParams;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbol;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeActionHandler;
|
||||
@@ -40,11 +42,13 @@ import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandle
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.InlayHintHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LanguageComputer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.LazyTextDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -65,8 +69,8 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeLanguageServerComponents build(SimpleLanguageServer server) {
|
||||
return new CompositeLanguageServerComponents(server, this);
|
||||
public CompositeLanguageServerComponents build(ApplicationContext appContext) {
|
||||
return new CompositeLanguageServerComponents(appContext, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +82,8 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
private final DocumentSymbolHandler docSymbolHandler;
|
||||
private final InlayHintHandler inlayHintHandler;
|
||||
|
||||
public CompositeLanguageServerComponents(SimpleLanguageServer server, Builder builder) {
|
||||
public CompositeLanguageServerComponents(ApplicationContext appContext, Builder builder) {
|
||||
SimpleLanguageServer server = appContext.getBean(SimpleLanguageServer.class);
|
||||
this.componentsByLanguageId = ImmutableMap.copyOf(builder.componentsByLanguageId);
|
||||
//Create composite Reconcile engine
|
||||
if (componentsByLanguageId.values().stream().flatMap(l -> l.stream()).map(LanguageServerComponents::getReconcileEngine).anyMatch(Optional::isPresent)) {
|
||||
@@ -145,7 +150,7 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
|
||||
@Override
|
||||
public List<? extends CodeLens> handle(CancelChecker cancelToken, CodeLensParams params) {
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri());
|
||||
TextDocument doc = getDoc(appContext, params.getTextDocument().getUri());
|
||||
LanguageId language = doc.getLanguageId();
|
||||
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
|
||||
if (subComponents != null) {
|
||||
@@ -183,17 +188,15 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
|
||||
@Override
|
||||
public List<? extends WorkspaceSymbol> handle(DocumentSymbolParams params) {
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri());
|
||||
if (doc != null) {
|
||||
LanguageId language = doc.getLanguageId();
|
||||
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
|
||||
if (subComponents != null) {
|
||||
return subComponents.stream()
|
||||
.map(sc -> sc.getDocumentSymbolProvider())
|
||||
.filter(ds -> ds.isPresent())
|
||||
.flatMap(ds -> ds.get().handle(params).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
TextDocument doc = getDoc(appContext, params.getTextDocument().getUri());
|
||||
LanguageId language = doc.getLanguageId();
|
||||
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
|
||||
if (subComponents != null) {
|
||||
return subComponents.stream()
|
||||
.map(sc -> sc.getDocumentSymbolProvider())
|
||||
.filter(ds -> ds.isPresent())
|
||||
.flatMap(ds -> ds.get().handle(params).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
//No applicable subEngine...
|
||||
return Collections.emptyList();
|
||||
@@ -244,5 +247,22 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
public Optional<InlayHintHandler> getInlayHintHandler() {
|
||||
return Optional.of(inlayHintHandler);
|
||||
}
|
||||
|
||||
private static TextDocument getDoc(ApplicationContext appContext, String uri) {
|
||||
|
||||
TextDocument doc = appContext.getBean(SimpleLanguageServer.class).getTextDocumentService().getLatestSnapshot(uri);
|
||||
|
||||
if (doc == null) {
|
||||
LanguageComputer languageComputer = appContext.getBean(LanguageComputer.class);
|
||||
if (languageComputer != null) {
|
||||
LanguageId language = languageComputer.computeLanguage(URI.create(uri));
|
||||
if (language != null) {
|
||||
doc = new LazyTextDocument(uri, language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* Copyright (c) 2022, 2024 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -39,12 +39,6 @@ public class LazyTextDocument extends TextDocument {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
// TODO Auto-generated method stub
|
||||
return super.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized Text getText() {
|
||||
if (!loaded) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Pivotal, Inc.
|
||||
* Copyright (c) 2015, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -13,6 +13,8 @@ package org.springframework.ide.vscode.commons.yaml.path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef;
|
||||
@@ -22,6 +24,8 @@ import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleValueRef;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.YamlPathSegmentType;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@@ -74,14 +78,49 @@ public class YamlPath extends AbstractYamlTraversal {
|
||||
* Parse a YamlPath from a dotted property name. The segments are obtained
|
||||
* by spliting the name at each dot.
|
||||
*/
|
||||
// public static YamlPath fromProperty(String propName) {
|
||||
// ArrayList<YamlPathSegment> segments = new ArrayList<YamlPathSegment>();
|
||||
// for (String s : propName.split("\\.")) {
|
||||
// segments.add(YamlPathSegment.valueAt(s));
|
||||
// }
|
||||
// return new YamlPath(segments);
|
||||
// }
|
||||
/**
|
||||
* Parse a YamlPath from a dotted property name. The segments are obtained
|
||||
* by splitting the name at each dot.
|
||||
*/
|
||||
public static YamlPath fromProperty(String propName) {
|
||||
ArrayList<YamlPathSegment> segments = new ArrayList<YamlPathSegment>();
|
||||
for (String s : propName.split("\\.")) {
|
||||
segments.add(YamlPathSegment.valueAt(s));
|
||||
ImmutableList.Builder<YamlPathSegment> segments = ImmutableList.builder();
|
||||
String delim = ".[]";
|
||||
StringTokenizer tokens = new StringTokenizer(propName, delim, true);
|
||||
try {
|
||||
while (tokens.hasMoreTokens()) {
|
||||
String token = tokens.nextToken(delim);
|
||||
if (token.equals(".") || token.equals("]")) {
|
||||
//Skip it silently
|
||||
} else if (token.equals("[")) {
|
||||
String bracketed = tokens.nextToken("]");
|
||||
if (bracketed.equals("]")) {
|
||||
//empty string between []? Makes no sense, so ignore that.
|
||||
} else {
|
||||
try {
|
||||
int index = Integer.parseInt(bracketed);
|
||||
segments.add(YamlPathSegment.valueAt(index));
|
||||
} catch (NumberFormatException e) {
|
||||
segments.add(YamlPathSegment.valueAt(bracketed));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
segments.add(YamlPathSegment.valueAt(token));
|
||||
}
|
||||
}
|
||||
} catch (NoSuchElementException e) {
|
||||
//Ran out of tokens.
|
||||
}
|
||||
return new YamlPath(segments);
|
||||
return new YamlPath(segments.build());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a YamlPath with a single segment (i.e. like 'fromProperty', but does
|
||||
* not parse '.' as segment separators.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -87,6 +87,8 @@ import org.eclipse.lsp4j.RegistrationParams;
|
||||
import org.eclipse.lsp4j.RenameFile;
|
||||
import org.eclipse.lsp4j.ResourceOperation;
|
||||
import org.eclipse.lsp4j.ResourceOperationKind;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.ShowMessageRequestParams;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
|
||||
@@ -442,6 +444,11 @@ public class LanguageServerHarness {
|
||||
public void liveProcessLogLevelUpdated(LiveProcessLoggersSummary processKey) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ShowDocumentResult> showDocument(ShowDocumentParams params) {
|
||||
return CompletableFuture.completedFuture(new ShowDocumentResult(true));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -810,15 +817,14 @@ public class LanguageServerHarness {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void perform(Command command) throws Exception {
|
||||
public Object perform(Command command) throws Exception {
|
||||
List<Object> args = command.getArguments();
|
||||
//Note convert the params to a 'typeless' Object because that is more representative on how it will be
|
||||
// received when we get it in a real client/server setting (i.e. parsed from json).
|
||||
JsonArray jsonArray = gson.toJsonTree(args).getAsJsonArray();
|
||||
List<Object> untypedParams = new ArrayList<>(jsonArray.size());
|
||||
jsonArray.forEach(e -> untypedParams.add(e));
|
||||
getServer().getWorkspaceService()
|
||||
return getServer().getWorkspaceService()
|
||||
.executeCommand(new ExecuteCommandParams(command.getCommand(), untypedParams))
|
||||
.get();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2024 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -129,7 +129,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
builder.add(c);
|
||||
}
|
||||
|
||||
components = builder.build(server);
|
||||
components = builder.build(appContext);
|
||||
|
||||
final SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
|
||||
|
||||
@@ -105,7 +105,12 @@ public class BootPropertiesLanguageServerComponents implements LanguageServerCom
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Register YAML -> Props conversion command
|
||||
new YamlToPropertiesCommand(server);
|
||||
|
||||
// Register Props -> YAML conversion command
|
||||
new PropertiesToYamlCommand(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.properties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.LazyTextDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
|
||||
public class PropertiesToYamlCommand {
|
||||
|
||||
static final String CMD_PROPS_TO_YAML = "sts/boot/props-to-yaml";
|
||||
|
||||
private static final Pattern COMMENT = Pattern.compile("(?m)^\\s*(\\#|\\!)");
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
|
||||
PropertiesToYamlCommand(SimpleLanguageServer server) {
|
||||
this.server = server;
|
||||
server.onCommand(CMD_PROPS_TO_YAML, params -> execute(params.getArguments()));
|
||||
}
|
||||
|
||||
private CompletableFuture<ShowDocumentResult> execute(List<Object> arguments) {
|
||||
String propsUri = arguments.get(0) instanceof JsonElement ? ((JsonElement) arguments.get(0)).getAsString() : (String) arguments.get(0);
|
||||
String yamlUri = arguments.get(1) instanceof JsonElement ? ((JsonElement) arguments.get(1)).getAsString() : (String) arguments.get(1);
|
||||
Boolean replace = arguments.get(2) instanceof JsonElement ? ((JsonElement) arguments.get(2)).getAsBoolean() : (Boolean) arguments.get(2);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return createWorkspaceEdit(propsUri, yamlUri, replace);
|
||||
} catch (IOException | BadLocationException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
})
|
||||
.thenCompose(we -> server.getClient().applyEdit(new ApplyWorkspaceEditParams(we, "Convert .properties to .yaml")))
|
||||
.thenCompose(res -> res.isApplied() ? server.getClient().showDocument(new ShowDocumentParams(yamlUri)) : CompletableFuture.completedFuture(new ShowDocumentResult(false)));
|
||||
}
|
||||
|
||||
private WorkspaceEdit createWorkspaceEdit(String propsUri, String yamlUri, Boolean replace) throws IOException, BadLocationException {
|
||||
Path yamlFile = Paths.get(URI.create(yamlUri));
|
||||
if (Files.exists(yamlFile)) {
|
||||
throw new IOException("File %s already exists!".formatted(yamlFile.toString()));
|
||||
}
|
||||
|
||||
TextDocument doc = getDocument(propsUri, LanguageId.BOOT_PROPERTIES);
|
||||
String propsContent = doc.get();
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
|
||||
if (hasComments(propsContent)) {
|
||||
warnings.add("The yaml file had comments which are lost in the refactoring!");
|
||||
}
|
||||
|
||||
Multimap<String, String> properties = load(new StringReader(propsContent));
|
||||
PropertiesToYamlConverter converter = new PropertiesToYamlConverter(properties);
|
||||
|
||||
StringBuilder yamlContent = new StringBuilder();
|
||||
errors.addAll(converter.getErrors());
|
||||
warnings.addAll(converter.getWarnings());
|
||||
YamlToPropertiesCommand.addReportHeaderComment(yamlContent, errors, warnings);
|
||||
yamlContent.append(converter.getYaml());
|
||||
|
||||
return replace ? YamlToPropertiesCommand.createReplaceFileWorkspaceEdit(propsUri, yamlUri, doc, yamlContent.toString()) : YamlToPropertiesCommand.createNewFileWorkspaceEdit(yamlUri, yamlContent.toString());
|
||||
}
|
||||
|
||||
private Multimap<String, String> load(Reader content) throws IOException {
|
||||
Multimap<String, String> map = Multimaps.newMultimap(
|
||||
new LinkedTreeMap<String, Collection<String>>(),
|
||||
LinkedHashSet::new
|
||||
);
|
||||
Properties loader = new Properties() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public synchronized Object put(Object key, Object value) {
|
||||
map.put((String)key, (String)value);
|
||||
return super.put(key, value);
|
||||
}
|
||||
};
|
||||
loader.load(content);
|
||||
return map;
|
||||
}
|
||||
|
||||
private static boolean hasComments(String propsContent) {
|
||||
return COMMENT.matcher(propsContent).find();
|
||||
}
|
||||
|
||||
private TextDocument getDocument(String uri, LanguageId language) {
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(uri);
|
||||
return doc == null ? new LazyTextDocument(uri, language) : doc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.properties;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.AtIndex;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
|
||||
/**
|
||||
* Helper class to convert (Spring Boot) .properties file content into equivalent
|
||||
* .yml file content.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
class PropertiesToYamlConverter {
|
||||
|
||||
private String output;
|
||||
private ImmutableList.Builder<String> errors;
|
||||
private ImmutableList.Builder<String> warnings;
|
||||
|
||||
class YamlBuilder {
|
||||
final YamlPath path;
|
||||
final List<Object> scalars = new ArrayList<>();
|
||||
final LinkedTreeMap<Integer, YamlBuilder> listItems = new LinkedTreeMap<>();
|
||||
final LinkedTreeMap<String, YamlBuilder> mapEntries = new LinkedTreeMap<>();
|
||||
|
||||
public YamlBuilder(YamlPath path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
void addProperty(YamlPath path, String value) {
|
||||
if (path.isEmpty()) {
|
||||
scalars.add(objectify(value));
|
||||
} else {
|
||||
YamlPathSegment segment = path.getSegment(0);
|
||||
YamlBuilder subBuilder;
|
||||
if (segment instanceof AtIndex) {
|
||||
subBuilder = getSubBuilder(listItems, segment, segment.toIndex());
|
||||
} else {
|
||||
subBuilder = getSubBuilder(mapEntries, segment, segment.toPropString());
|
||||
}
|
||||
subBuilder.addProperty(path.dropFirst(1), value);
|
||||
}
|
||||
}
|
||||
|
||||
private Object objectify(String value) {
|
||||
if (value != null) {
|
||||
Object parsed = null;
|
||||
try {
|
||||
parsed = new BigInteger(value);
|
||||
} catch (NumberFormatException e) {
|
||||
try {
|
||||
parsed = new BigDecimal(value);
|
||||
} catch (NumberFormatException e2) {
|
||||
if (value.equals("true")) {
|
||||
parsed = true;
|
||||
} else if (value.equals("false")) {
|
||||
parsed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parsed!=null && parsed.toString().equals(value)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private <T> YamlBuilder getSubBuilder(LinkedTreeMap<T, YamlBuilder> subBuilders, YamlPathSegment segment, T key) {
|
||||
YamlBuilder existing = subBuilders.get(key);
|
||||
if (existing==null) {
|
||||
subBuilders.put(key, existing = new YamlBuilder(path.append(segment)));
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
public Object build() {
|
||||
if (!scalars.isEmpty()) {
|
||||
if (listItems.isEmpty() && mapEntries.isEmpty()) {
|
||||
if (scalars.size()>1) {
|
||||
warnings.add("Multiple values "+ scalars +" assigned to '"+path.toPropString()+"'. Values are merged into a yaml sequence node.");
|
||||
return scalars;
|
||||
} else {
|
||||
return scalars.get(0);
|
||||
}
|
||||
} else {
|
||||
if (!mapEntries.isEmpty()) {
|
||||
errors.add(
|
||||
"Direct assignment '"+path.toPropString()+"="+scalars.get(0)+"' can not be combined " +
|
||||
"with sub-property assignment '"+path.toPropString()+"." + mapEntries.keySet().iterator().next()+"...'. "+
|
||||
"Direct assignment is dropped!"
|
||||
);
|
||||
} else {
|
||||
errors.add(
|
||||
"Direct assignment '"+path.toPropString()+"="+scalars.get(0)+"' can not be combined " +
|
||||
"with sequence assignment '"+path.toPropString()+"[" + listItems.keySet().iterator().next()+"]...'. "+
|
||||
"Direct assignments are dropped!"
|
||||
);
|
||||
}
|
||||
scalars.clear();
|
||||
}
|
||||
}
|
||||
Assert.isLegal(scalars.isEmpty());
|
||||
if (!listItems.isEmpty() && !mapEntries.isEmpty()) {
|
||||
warnings.add("'"+path.toPropString()+"' has some entries that look like list items and others that look like map entries. "
|
||||
+ "All these entries are treated as map entries!");
|
||||
for (Entry<Integer, YamlBuilder> listItem : listItems.entrySet()) {
|
||||
mapEntries.put(listItem.getKey().toString(), listItem.getValue());
|
||||
}
|
||||
listItems.clear();
|
||||
}
|
||||
if (!listItems.isEmpty()) {
|
||||
return listItems.values().stream()
|
||||
.map(childBuilder -> childBuilder.build())
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
LinkedTreeMap<String, Object> map = new LinkedTreeMap<>();
|
||||
for (Entry<String, YamlBuilder> entry : mapEntries.entrySet()) {
|
||||
map.put(entry.getKey(), entry.getValue().build());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PropertiesToYamlConverter(Multimap<String, String> properties) {
|
||||
this.errors = ImmutableList.builder();
|
||||
this.warnings = ImmutableList.builder();
|
||||
if (properties.isEmpty()) {
|
||||
output = "";
|
||||
return;
|
||||
}
|
||||
YamlBuilder root = new YamlBuilder(YamlPath.EMPTY);
|
||||
for (Entry<String, String> e : properties.entries()) {
|
||||
root.addProperty(YamlPath.fromProperty(e.getKey()), e.getValue());
|
||||
}
|
||||
Object object = root.build();
|
||||
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
options.setPrettyFlow(true);
|
||||
|
||||
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(options), options);
|
||||
this.output = yaml.dump(object);
|
||||
}
|
||||
|
||||
public List<String> getErrors() {
|
||||
return errors.build();
|
||||
}
|
||||
|
||||
public List<String> getWarnings() {
|
||||
return warnings.build();
|
||||
}
|
||||
|
||||
public String getYaml() {
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.properties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
import org.eclipse.lsp4j.AnnotatedTextEdit;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.ChangeAnnotation;
|
||||
import org.eclipse.lsp4j.CreateFile;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.RenameFile;
|
||||
import org.eclipse.lsp4j.ShowDocumentParams;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.TextDocumentEdit;
|
||||
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.LazyTextDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.comments.CommentType;
|
||||
import org.yaml.snakeyaml.events.CommentEvent;
|
||||
import org.yaml.snakeyaml.events.Event;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
class YamlToPropertiesCommand {
|
||||
|
||||
private static final String LABEL = "Convert .yaml to .properties";
|
||||
|
||||
static final String CMD_YAML_TO_PROPS = "sts/boot/yaml-to-props";
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
|
||||
YamlToPropertiesCommand(SimpleLanguageServer server) {
|
||||
this.server = server;
|
||||
server.onCommand(CMD_YAML_TO_PROPS, params -> execute(params.getArguments()));
|
||||
}
|
||||
|
||||
private CompletableFuture<ShowDocumentResult> execute(List<Object> arguments) {
|
||||
String yamlUri = arguments.get(0) instanceof JsonElement ? ((JsonElement) arguments.get(0)).getAsString() : (String) arguments.get(0);
|
||||
String propsUri = arguments.get(1) instanceof JsonElement ? ((JsonElement) arguments.get(1)).getAsString() : (String) arguments.get(1);
|
||||
Boolean replace = arguments.get(2) instanceof JsonElement ? ((JsonElement) arguments.get(2)).getAsBoolean() : (Boolean) arguments.get(2);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return createWorkspaceEdit(yamlUri, propsUri, replace);
|
||||
} catch (IOException | BadLocationException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
})
|
||||
.thenCompose(we -> server.getClient().applyEdit(new ApplyWorkspaceEditParams(we, LABEL)))
|
||||
.thenCompose(res -> res.isApplied() ? server.getClient().showDocument(new ShowDocumentParams(propsUri)) : CompletableFuture.completedFuture(new ShowDocumentResult(false)));
|
||||
}
|
||||
|
||||
private WorkspaceEdit createWorkspaceEdit(String yamlUri, String propsUri, boolean replace) throws IOException, BadLocationException {
|
||||
Path propsFile = Paths.get(URI.create(propsUri));
|
||||
if (Files.exists(propsFile)) {
|
||||
throw new IOException("File %s already exists!".formatted(propsFile.toString()));
|
||||
}
|
||||
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
TextDocument doc = getDocument(server, yamlUri, LanguageId.BOOT_PROPERTIES_YAML);
|
||||
String yamlContent = doc.get();
|
||||
|
||||
if (hasComments(yamlContent)) {
|
||||
warnings.add("The yaml file had comments which are lost in the refactoring!");
|
||||
}
|
||||
|
||||
StringBuilder propsContent = new StringBuilder();
|
||||
for (Object d : new Yaml().loadAll(new StringReader(yamlContent))) {
|
||||
if (d instanceof Map) {
|
||||
// Add doc divider if not empty
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> o = (Map<String, ?>) d;
|
||||
YamlToPropertiesConverter converter = new YamlToPropertiesConverter(o);
|
||||
Properties props = converter.getProperties();
|
||||
StringWriter write = new StringWriter();
|
||||
props.store(write, null);
|
||||
if (!propsContent.isEmpty()) {
|
||||
propsContent.append("#---\n");
|
||||
} else {
|
||||
addReportHeaderComment(propsContent, errors, warnings);
|
||||
}
|
||||
// Skip over the date header. Comments are not present but date header is.
|
||||
if (write.getBuffer().charAt(0) == '#') {
|
||||
int idx = write.getBuffer().indexOf("\n");
|
||||
propsContent.append(idx >= 0 && idx < write.getBuffer().length() ? write.getBuffer().substring(idx + 1) : write.getBuffer().toString());
|
||||
} else {
|
||||
propsContent.append(write.getBuffer().toString());
|
||||
}
|
||||
} else if (d == null) {
|
||||
if (!propsContent.isEmpty()) {
|
||||
propsContent.append("#---\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return replace ? createReplaceFileWorkspaceEdit(yamlUri, propsUri, doc, propsContent.toString()) : createNewFileWorkspaceEdit(propsUri, propsContent.toString());
|
||||
}
|
||||
|
||||
|
||||
private static boolean hasComments(String yamlContent) {
|
||||
LoaderOptions loaderOptions = new LoaderOptions();
|
||||
loaderOptions.setProcessComments(true);
|
||||
for (Event e : new Yaml(loaderOptions).parse(new StringReader(yamlContent))) {
|
||||
if (e instanceof CommentEvent ce) {
|
||||
if (ce.getCommentType() == CommentType.BLANK_LINE) {
|
||||
// document separator
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static WorkspaceEdit createReplaceFileWorkspaceEdit(String sourceUri, String targetUri, TextDocument oldDoc, String newContent) throws BadLocationException {
|
||||
String changeAnnotationId = UUID.randomUUID().toString();
|
||||
ChangeAnnotation changeAnnotation = new ChangeAnnotation(LABEL);
|
||||
changeAnnotation.setNeedsConfirmation(true);
|
||||
|
||||
RenameFile renameFile = new RenameFile(sourceUri, targetUri);
|
||||
renameFile.setAnnotationId(changeAnnotationId);
|
||||
WorkspaceEdit we = new WorkspaceEdit(List.of(
|
||||
Either.forLeft(new TextDocumentEdit(
|
||||
new VersionedTextDocumentIdentifier(sourceUri, oldDoc.getVersion()),
|
||||
List.of(new AnnotatedTextEdit(oldDoc.toRange(0, oldDoc.getLength()), newContent, changeAnnotationId))
|
||||
)),
|
||||
Either.forRight(renameFile)
|
||||
));
|
||||
we.setChangeAnnotations(Map.of(changeAnnotationId, changeAnnotation));
|
||||
return we;
|
||||
}
|
||||
|
||||
static WorkspaceEdit createNewFileWorkspaceEdit(String uri, String content) {
|
||||
String changeAnnotationId = UUID.randomUUID().toString();
|
||||
ChangeAnnotation changeAnnotation = new ChangeAnnotation(LABEL);
|
||||
changeAnnotation.setNeedsConfirmation(false); // VSCode refactor preview errors out showing diff for newly added file
|
||||
|
||||
CreateFile createFile = new CreateFile(uri);
|
||||
createFile.setAnnotationId(changeAnnotationId);
|
||||
WorkspaceEdit we = new WorkspaceEdit(List.of(
|
||||
Either.forRight(createFile),
|
||||
Either.forLeft(new TextDocumentEdit(
|
||||
new VersionedTextDocumentIdentifier(uri, null),
|
||||
List.of(new AnnotatedTextEdit(new Range(new Position(0,0), new Position(0,0)), content, changeAnnotationId))
|
||||
))
|
||||
));
|
||||
we.setChangeAnnotations(Map.of(changeAnnotationId, changeAnnotation));
|
||||
return we;
|
||||
}
|
||||
|
||||
static TextDocument getDocument(SimpleLanguageServer server, String uri, LanguageId language) {
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(uri);
|
||||
return doc == null ? new LazyTextDocument(uri, language) : doc;
|
||||
}
|
||||
|
||||
static void addReportHeaderComment(StringBuilder content, List<String> errors, List<String> warnings) {
|
||||
if (!errors.isEmpty() || !warnings.isEmpty()) {
|
||||
content.append("# Conversion to YAML from Properties formar report\n");
|
||||
if (!errors.isEmpty()) {
|
||||
content.append("# Errors:\n");
|
||||
for (String e : errors) {
|
||||
content.append("# - %s\n".formatted(e));
|
||||
}
|
||||
}
|
||||
if (!warnings.isEmpty()) {
|
||||
content.append("# Warnings:\n");
|
||||
for (String w : warnings) {
|
||||
content.append("# - %s\n".formatted(w));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.properties;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
class YamlToPropertiesConverter {
|
||||
|
||||
private final Properties properties;
|
||||
|
||||
|
||||
public YamlToPropertiesConverter(Map<String, ?> yaml) {
|
||||
this.properties = new Properties() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private LinkedHashMap<Object, Object> delegate = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized Object put(Object key, Object value) {
|
||||
delegate.put(key, value);
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<Object, Object>> entrySet() {
|
||||
return delegate.entrySet();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
for (Map.Entry<String, ?> e : yaml.entrySet()) {
|
||||
readProperties(e.getValue(), e.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private void readPropertiesFromYamlMap(Map<String, ?> map, String prefix) {
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
readProperties(e.getValue(), "%s.%s".formatted(prefix, e.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
private void readPropertiesFromYamlList(List<?> l, String prefix) {
|
||||
for (int i = 0; i < l.size(); i++) {
|
||||
readProperties(l.get(i), "%s[%d]".formatted(prefix, i));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void readProperties(Object o, String prefix) {
|
||||
if (o instanceof Map) {
|
||||
readPropertiesFromYamlMap((Map<String, ?>) o, prefix);
|
||||
} else if ( o instanceof List) {
|
||||
readPropertiesFromYamlList((List<?>) o, prefix);
|
||||
} else {
|
||||
properties.put(prefix, o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.properties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import({SymbolProviderTestConf.class})
|
||||
public class PropertiesToYamlCommandTest {
|
||||
|
||||
private static final String FILENAME_SUFFIX = "-conversion-test";
|
||||
|
||||
@Autowired private LanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
|
||||
private File directory;
|
||||
|
||||
@BeforeEach public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-validations/").toURI());
|
||||
|
||||
Files.walk(directory.toPath(), Integer.MAX_VALUE).filter(Files::isRegularFile).filter(p -> p.getFileName().toString().contains(FILENAME_SUFFIX)).forEach(t -> {
|
||||
try {
|
||||
Files.delete(t);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
}
|
||||
|
||||
@AfterEach public void tearDown() throws Exception {
|
||||
if (directory != null && directory.exists()) {
|
||||
Files.walk(directory.toPath(), Integer.MAX_VALUE).filter(Files::isRegularFile).filter(p -> p.getFileName().toString().contains(FILENAME_SUFFIX)).forEach(t -> {
|
||||
try {
|
||||
Files.delete(t);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void assertConversion(String propsContent, String yamlContent, boolean replace) throws Exception {
|
||||
Path yamlFilePath = directory.toPath().resolve("src/main/resources/application" + FILENAME_SUFFIX + ".yml");
|
||||
Path propsFilePath = directory.toPath().resolve("src/main/resources/application" + FILENAME_SUFFIX + ".properties");
|
||||
|
||||
if (!Files.exists(propsFilePath)) {
|
||||
Files.createDirectories(propsFilePath.getParent());
|
||||
Files.createFile(propsFilePath);
|
||||
Files.write(propsFilePath, propsContent.getBytes(), StandardOpenOption.APPEND);
|
||||
}
|
||||
|
||||
Command cmd = new Command();
|
||||
cmd.setCommand(PropertiesToYamlCommand.CMD_PROPS_TO_YAML);
|
||||
cmd.setArguments(List.of(propsFilePath.toUri().toASCIIString(), yamlFilePath.toUri().toASCIIString(), replace));
|
||||
cmd.setTitle("Convert .propeties to .yaml");
|
||||
|
||||
ShowDocumentResult res = (ShowDocumentResult) harness.perform(cmd);
|
||||
|
||||
assertThat(res.isSuccess()).isTrue();
|
||||
|
||||
assertThat(Files.exists(yamlFilePath)).isTrue();
|
||||
if (replace) {
|
||||
assertThat(Files.exists(propsFilePath)).isFalse();
|
||||
} else {
|
||||
assertThat(Files.exists(propsFilePath)).isTrue();
|
||||
}
|
||||
|
||||
assertThat(Files.readString(yamlFilePath)).isEqualTo(yamlContent);
|
||||
}
|
||||
|
||||
@Test void almostHasComments() throws Exception {
|
||||
assertConversion(
|
||||
"my.hello=Good morning!\n" +
|
||||
"my.goodbye=See ya # later\n"
|
||||
, // ==>
|
||||
"my:\n" +
|
||||
" hello: Good morning!\n" +
|
||||
" goodbye: 'See ya # later'\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test void listItems() throws Exception {
|
||||
assertConversion(
|
||||
"some.thing[0].a=first-a\n" +
|
||||
"some.thing[0].b=first-b\n" +
|
||||
"some.thing[1].a=second-a\n" +
|
||||
"some.thing[1].b=second-b\n"
|
||||
, // ==>
|
||||
"some:\n" +
|
||||
" thing:\n" +
|
||||
" - a: first-a\n" +
|
||||
" b: first-b\n" +
|
||||
" - a: second-a\n" +
|
||||
" b: second-b\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void simple() throws Exception {
|
||||
assertConversion(
|
||||
"some.thing=vvvv\n" +
|
||||
"some.other.thing=blah\n"
|
||||
, // ==>
|
||||
"some:\n" +
|
||||
" thing: vvvv\n" +
|
||||
" other:\n" +
|
||||
" thing: blah\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void noReplacement() throws Exception {
|
||||
assertConversion(
|
||||
"some.thing=vvvv\n" +
|
||||
"some.other.thing=blah\n"
|
||||
, // ==>
|
||||
"some:\n" +
|
||||
" thing: vvvv\n" +
|
||||
" other:\n" +
|
||||
" thing: blah\n",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@Test void nonStringyValueConversion() throws Exception {
|
||||
//See: https://www.pivotaltracker.com/story/show/154181583
|
||||
//Test that we do not add unnecessary quotes around certain types of values.
|
||||
assertConversion(
|
||||
"exponated=123.4E-12\n" +
|
||||
"server.port=8888\n" +
|
||||
"foobar.enabled=true\n" +
|
||||
"foobar.nice=false\n" +
|
||||
"fractional=0.78\n" +
|
||||
"largenumber=989898989898989898989898989898989898989898989898989898989898\n" +
|
||||
"longfractional=-0.989898989898989898989898989898989898989898989898989898989898\n"
|
||||
, // ==>
|
||||
"exponated: '123.4E-12'\n" + //quotes are added because conversion to number changes the string value
|
||||
"server:\n" +
|
||||
" port: 8888\n" +
|
||||
"foobar:\n" +
|
||||
" enabled: true\n" +
|
||||
" nice: false\n" +
|
||||
"fractional: 0.78\n" +
|
||||
"largenumber: 989898989898989898989898989898989898989898989898989898989898\n" +
|
||||
"longfractional: -0.989898989898989898989898989898989898989898989898989898989898\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void emptyFileConversion() throws Exception {
|
||||
assertConversion(
|
||||
""
|
||||
, // ==>
|
||||
"",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void multipleAssignmentProblem() throws Exception {
|
||||
assertConversion(
|
||||
"some.property=something\n" +
|
||||
"some.property=something-else"
|
||||
, // ==>
|
||||
"# Conversion to YAML from Properties formar report\n" +
|
||||
"# Warnings:\n" +
|
||||
"# - Multiple values [something, something-else] assigned to 'some.property'. Values are merged into a yaml sequence node.\n" +
|
||||
"some:\n" +
|
||||
" property:\n" +
|
||||
" - something\n" +
|
||||
" - something-else\n"
|
||||
,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void scalarAndMapConflict() throws Exception {
|
||||
assertConversion(
|
||||
"some.property=a-scalar\n" +
|
||||
"some.property.sub=sub-value"
|
||||
,
|
||||
"# Conversion to YAML from Properties formar report\n" +
|
||||
"# Errors:\n" +
|
||||
"# - Direct assignment 'some.property=a-scalar' can not be combined with sub-property assignment 'some.property.sub...'. Direct assignment is dropped!\n" +
|
||||
"some:\n" +
|
||||
" property:\n" +
|
||||
" sub: sub-value\n"
|
||||
,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void scalarAndSequenceConflict() throws Exception {
|
||||
assertConversion(
|
||||
"some.property=a-scalar\n" +
|
||||
"some.property[0]=zero\n" +
|
||||
"some.property[1]=one\n"
|
||||
,
|
||||
"# Conversion to YAML from Properties formar report\n" +
|
||||
"# Errors:\n" +
|
||||
"# - Direct assignment 'some.property=a-scalar' can not be combined with sequence assignment 'some.property[0]...'. Direct assignments are dropped!\n" +
|
||||
"some:\n" +
|
||||
" property:\n" +
|
||||
" - zero\n" +
|
||||
" - one\n"
|
||||
,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void mapAndSequenceConflict() throws Exception {
|
||||
assertConversion(
|
||||
"some.property.abc=val1\n" +
|
||||
"some.property.def=val2\n" +
|
||||
"some.property[0]=zero\n" +
|
||||
"some.property[1]=one\n"
|
||||
,
|
||||
"# Conversion to YAML from Properties formar report\n" +
|
||||
"# Warnings:\n" +
|
||||
"# - 'some.property' has some entries that look like list items and others that look like map entries. All these entries are treated as map entries!\n" +
|
||||
"some:\n" +
|
||||
" property:\n" +
|
||||
" abc: val1\n" +
|
||||
" def: val2\n" +
|
||||
" '0': zero\n" +
|
||||
" '1': one\n"
|
||||
,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void scalarAndMapAndSequenceConflict() throws Exception {
|
||||
assertConversion(
|
||||
"some.property=a-scalar\n" +
|
||||
"some.property.abc=val1\n" +
|
||||
"some.property.def=val2\n" +
|
||||
"some.property[0]=zero\n" +
|
||||
"some.property[1]=one\n"
|
||||
,
|
||||
"# Conversion to YAML from Properties formar report\n" +
|
||||
"# Errors:\n" +
|
||||
"# - Direct assignment 'some.property=a-scalar' can not be combined with sub-property assignment 'some.property.abc...'. Direct assignment is dropped!\n" +
|
||||
"# Warnings:\n" +
|
||||
"# - 'some.property' has some entries that look like list items and others that look like map entries. All these entries are treated as map entries!\n" +
|
||||
"some:\n" +
|
||||
" property:\n" +
|
||||
" abc: val1\n" +
|
||||
" def: val2\n" +
|
||||
" '0': zero\n" +
|
||||
" '1': one\n"
|
||||
,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void lineComment() throws Exception {
|
||||
assertConversion(
|
||||
"some.thing=vvvv\n" +
|
||||
"# Line comment\n" +
|
||||
"some.other.thing=blah\n"
|
||||
, // ==>
|
||||
"# Conversion to YAML from Properties formar report\n" +
|
||||
"# Warnings:\n" +
|
||||
"# - The yaml file had comments which are lost in the refactoring!\n" +
|
||||
"some:\n" +
|
||||
" thing: vvvv\n" +
|
||||
" other:\n" +
|
||||
" thing: blah\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.properties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.ShowDocumentResult;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import({SymbolProviderTestConf.class})
|
||||
public class YamlToPropertiesCommandTest {
|
||||
|
||||
private static final String FILENAME_SUFFIX = "-conversion-test";
|
||||
|
||||
@Autowired private LanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
|
||||
private File directory;
|
||||
|
||||
@BeforeEach public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-validations/").toURI());
|
||||
|
||||
Files.walk(directory.toPath(), Integer.MAX_VALUE).filter(Files::isRegularFile).filter(p -> p.getFileName().toString().contains(FILENAME_SUFFIX)).forEach(t -> {
|
||||
try {
|
||||
Files.delete(t);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
}
|
||||
|
||||
@AfterEach public void tearDown() throws Exception {
|
||||
if (directory != null && directory.exists()) {
|
||||
Files.walk(directory.toPath(), Integer.MAX_VALUE).filter(Files::isRegularFile).filter(p -> p.getFileName().toString().contains(FILENAME_SUFFIX)).forEach(t -> {
|
||||
try {
|
||||
Files.delete(t);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test void simple() throws Exception {
|
||||
assertConversion("""
|
||||
some:
|
||||
other:
|
||||
thing: blah
|
||||
thing: vvvv
|
||||
""",
|
||||
"""
|
||||
some.other.thing=blah
|
||||
some.thing=vvvv
|
||||
""",
|
||||
true);
|
||||
}
|
||||
|
||||
@Test void noReplacement() throws Exception {
|
||||
assertConversion("""
|
||||
some:
|
||||
other:
|
||||
thing: blah
|
||||
thing: vvvv
|
||||
""",
|
||||
"""
|
||||
some.other.thing=blah
|
||||
some.thing=vvvv
|
||||
""",
|
||||
false);
|
||||
}
|
||||
|
||||
@Test public void almostHasComments() throws Exception {
|
||||
assertConversion(
|
||||
"my:\n" +
|
||||
" goodbye: 'See ya # later'\n" +
|
||||
" hello: Good morning!\n"
|
||||
, // ==>
|
||||
"my.goodbye=See ya \\# later\n" +
|
||||
"my.hello=Good morning\\!\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void listItems() throws Exception {
|
||||
assertConversion(
|
||||
"some:\n" +
|
||||
" thing:\n" +
|
||||
" - a: first-a\n" +
|
||||
" b: first-b\n" +
|
||||
" - a: second-a\n" +
|
||||
" b: second-b\n"
|
||||
, // ==>
|
||||
"some.thing[0].a=first-a\n" +
|
||||
"some.thing[0].b=first-b\n" +
|
||||
"some.thing[1].a=second-a\n" +
|
||||
"some.thing[1].b=second-b\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void list() throws Exception {
|
||||
assertConversion(
|
||||
"some:\n" +
|
||||
" property:\n" +
|
||||
" - something\n" +
|
||||
" - something-else\n"
|
||||
, // ==>
|
||||
"some.property[0]=something\n" +
|
||||
"some.property[1]=something-else\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void mapAndSequenceConflict() throws Exception {
|
||||
assertConversion(
|
||||
"some:\n" +
|
||||
" property:\n" +
|
||||
" '0': zero\n" +
|
||||
" '1': one\n" +
|
||||
" abc: val1\n" +
|
||||
" def: val2\n"
|
||||
,
|
||||
"some.property.0=zero\n" +
|
||||
"some.property.1=one\n" +
|
||||
"some.property.abc=val1\n" +
|
||||
"some.property.def=val2\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void multipleDocsConversion() throws Exception {
|
||||
assertConversion(
|
||||
"some:\n" +
|
||||
" other:\n" +
|
||||
" thing: blah\n" +
|
||||
" thing: vvvv\n" +
|
||||
"\n" +
|
||||
"---\n" +
|
||||
"some:\n" +
|
||||
" other:\n" +
|
||||
" thing: blah\n" +
|
||||
" thing: vvvv\n" +
|
||||
"\n" +
|
||||
"---\n" +
|
||||
"some:\n" +
|
||||
" other:\n" +
|
||||
" thing: blah\n" +
|
||||
" thing: vvvv\n"
|
||||
, // ==>
|
||||
"some.other.thing=blah\n" +
|
||||
"some.thing=vvvv\n" +
|
||||
"#---\n" +
|
||||
"some.other.thing=blah\n" +
|
||||
"some.thing=vvvv\n" +
|
||||
"#---\n" +
|
||||
"some.other.thing=blah\n" +
|
||||
"some.thing=vvvv\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Test void lineComment() throws Exception {
|
||||
assertConversion("""
|
||||
some:
|
||||
# Comment about line
|
||||
other:
|
||||
thing: blah
|
||||
thing: vvvv
|
||||
""",
|
||||
"""
|
||||
# Conversion to YAML from Properties formar report
|
||||
# Warnings:
|
||||
# - The yaml file had comments which are lost in the refactoring!
|
||||
some.other.thing=blah
|
||||
some.thing=vvvv
|
||||
""",
|
||||
true);
|
||||
}
|
||||
|
||||
@Test void inlineComment() throws Exception {
|
||||
assertConversion("""
|
||||
some:
|
||||
other:
|
||||
thing: blah # Inline Comment
|
||||
thing: vvvv
|
||||
""",
|
||||
"""
|
||||
# Conversion to YAML from Properties formar report
|
||||
# Warnings:
|
||||
# - The yaml file had comments which are lost in the refactoring!
|
||||
some.other.thing=blah
|
||||
some.thing=vvvv
|
||||
""",
|
||||
true);
|
||||
}
|
||||
|
||||
void assertConversion(String yamlContent, String propsContent, boolean replace) throws Exception {
|
||||
Path yamlFilePath = directory.toPath().resolve("src/main/resources/application" + FILENAME_SUFFIX + ".yml");
|
||||
Path propsFilePath = directory.toPath().resolve("src/main/resources/application" + FILENAME_SUFFIX + ".properties");
|
||||
|
||||
if (!Files.exists(yamlFilePath)) {
|
||||
Files.createDirectories(yamlFilePath.getParent());
|
||||
Files.createFile(yamlFilePath);
|
||||
Files.write(yamlFilePath, yamlContent.getBytes(), StandardOpenOption.APPEND);
|
||||
}
|
||||
|
||||
Command cmd = new Command();
|
||||
cmd.setCommand(YamlToPropertiesCommand.CMD_YAML_TO_PROPS);
|
||||
cmd.setArguments(List.of(yamlFilePath.toUri().toASCIIString(), propsFilePath.toUri().toASCIIString(), replace));
|
||||
cmd.setTitle("Convert .yaml to .properties");
|
||||
|
||||
ShowDocumentResult res = (ShowDocumentResult) harness.perform(cmd);
|
||||
|
||||
assertThat(res.isSuccess()).isTrue();
|
||||
|
||||
assertThat(Files.exists(propsFilePath)).isTrue();
|
||||
if (replace) {
|
||||
assertThat(Files.exists(yamlFilePath)).isFalse();
|
||||
} else {
|
||||
assertThat(Files.exists(yamlFilePath)).isTrue();
|
||||
}
|
||||
|
||||
assertThat(Files.readString(propsFilePath)).isEqualTo(propsContent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import {registerClasspathService} from "@pivotal-tools/commons-vscode/lib/classp
|
||||
import {registerJavaDataService} from "@pivotal-tools/commons-vscode/lib/java-data";
|
||||
import * as setLogLevelUi from './set-log-levels-ui';
|
||||
import { startTestJarSupport } from "./test-jar-launch";
|
||||
import { startPropertiesConversionSupport } from "./convert-props-yaml";
|
||||
|
||||
const PROPERTIES_LANGUAGE_ID = "spring-boot-properties";
|
||||
const YAML_LANGUAGE_ID = "spring-boot-properties-yaml";
|
||||
@@ -157,7 +158,16 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
|
||||
liveHoverUi.activate(client, options, context);
|
||||
rewrite.activate(client, options, context);
|
||||
setLogLevelUi.activate(client, options, context);
|
||||
startPropertiesConversionSupport(context);
|
||||
|
||||
registerMiscCommands(context);
|
||||
|
||||
return new ApiManager(client).api;
|
||||
});
|
||||
}
|
||||
|
||||
function registerMiscCommands(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand('vscode-spring-boot.spring.modulith.metadata.refresh', async () => {
|
||||
const modulithProjects = await commands.executeCommand('sts/modulith/projects');
|
||||
const projectNames = Object.keys(modulithProjects);
|
||||
@@ -170,14 +180,12 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
|
||||
);
|
||||
commands.executeCommand('sts/modulith/metadata/refresh', modulithProjects[projectName]);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
commands.registerCommand('vscode-spring-boot.open.url', (openUrl) => {
|
||||
const openWithExternalBrowser = workspace.getConfiguration("spring.tools").get("openWith") === "external";
|
||||
const browserCommand = openWithExternalBrowser ? "vscode.open" : "simpleBrowser.api.open";
|
||||
return commands.executeCommand(browserCommand, Uri.parse(openUrl));
|
||||
});
|
||||
|
||||
return new ApiManager(client).api;
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as path from "path";
|
||||
import { existsSync } from "fs";
|
||||
import { ExtensionContext, Uri, commands, window, workspace } from "vscode";
|
||||
|
||||
export function startPropertiesConversionSupport(extension: ExtensionContext) {
|
||||
extension.subscriptions.push(
|
||||
commands.registerCommand('vscode-spring-boot.props-to-yaml', async (uri) => {
|
||||
|
||||
if (!uri && window.activeTextEditor) {
|
||||
const activeUri = window.activeTextEditor.document.uri;
|
||||
if (".properties" === path.extname(activeUri.path)) {
|
||||
uri = activeUri;
|
||||
}
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
throw new Error("No '.properties' file selected");
|
||||
}
|
||||
|
||||
return await commands.executeCommand("sts/boot/props-to-yaml", uri.toString(), Uri.file(getTargetFile(uri.path, "yml")).toString(), shouldReplace());
|
||||
}),
|
||||
|
||||
commands.registerCommand('vscode-spring-boot.yaml-to-props', async (uri) => {
|
||||
|
||||
if (!uri && window.activeTextEditor) {
|
||||
const activeUri = window.activeTextEditor.document.uri;
|
||||
const ext = path.extname(activeUri.path)
|
||||
if (".yml" === ext || ".yaml" === ext) {
|
||||
uri = activeUri;
|
||||
}
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
throw new Error("No '.yaml' file selected");
|
||||
}
|
||||
|
||||
return await commands.executeCommand("sts/boot/yaml-to-props", uri.toString(), Uri.file(getTargetFile(uri.path, "properties")).toString(), shouldReplace());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getTargetFile(sourcePath: string, ext: string): string {
|
||||
const dir = path.dirname(sourcePath);
|
||||
const fileName = path.basename(sourcePath);
|
||||
const filenameNoExt = path.basename(sourcePath).substring(0, fileName.length - path.extname(fileName).length);
|
||||
let targetPath = path.join(dir, `${filenameNoExt}.${ext}`);
|
||||
for (let i = 1; i < Number.MAX_SAFE_INTEGER && existsSync(targetPath); i++) {
|
||||
targetPath = path.join(dir, `${filenameNoExt}-${i}.${ext}`)
|
||||
}
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
function shouldReplace(): boolean {
|
||||
return workspace.getConfiguration("spring.tools.properties").get("replace-converted-file");
|
||||
}
|
||||
|
||||
@@ -82,6 +82,16 @@
|
||||
"when": "resourceFilename == pom.xml || resourceFilename == build.gradle",
|
||||
"command": "vscode-spring-boot.rewrite.list.boot-upgrades",
|
||||
"group": "SpringBoot"
|
||||
},
|
||||
{
|
||||
"when": "editorLangId == spring-boot-properties",
|
||||
"command": "vscode-spring-boot.props-to-yaml",
|
||||
"group": "SpringBoot"
|
||||
},
|
||||
{
|
||||
"when": "editorLangId == spring-boot-properties-yaml",
|
||||
"command": "vscode-spring-boot.yaml-to-props",
|
||||
"group": "SpringBoot"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
@@ -94,6 +104,26 @@
|
||||
"when": "(resourceFilename == pom.xml || resourceFilename == build.gradle) && config.boot-java.rewrite.refactorings.on == true",
|
||||
"command": "vscode-spring-boot.rewrite.list.boot-upgrades",
|
||||
"group": "SpringBoot"
|
||||
},
|
||||
{
|
||||
"when": "resourceExtname == .properties",
|
||||
"command": "vscode-spring-boot.props-to-yaml",
|
||||
"group": "SpringBoot"
|
||||
},
|
||||
{
|
||||
"when": "resourceExtname == .yml || resourceExtname == .yaml",
|
||||
"command": "vscode-spring-boot.yaml-to-props",
|
||||
"group": "SpringBoot"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "vscode-spring-boot.props-to-yaml",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.yaml-to-props",
|
||||
"when": "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -140,6 +170,16 @@
|
||||
"title": "Open Boot App Page URL",
|
||||
"category": "Spring Boot",
|
||||
"enablement": "never"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.props-to-yaml",
|
||||
"title": "Convert .properties to .yaml",
|
||||
"category": "Spring Boot"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.yaml-to-props",
|
||||
"title": "Convert .yaml to .properties",
|
||||
"category": "Spring Boot"
|
||||
}
|
||||
],
|
||||
"configuration": [
|
||||
@@ -245,6 +285,11 @@
|
||||
"default": true,
|
||||
"description": "Enable/Disable Spring Boot TestJars launch environment variables"
|
||||
},
|
||||
"spring.tools.properties.replace-converted-file": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Replace converted properties file"
|
||||
},
|
||||
"spring.tools.openWith": {
|
||||
"default": "integrated",
|
||||
"type": "string",
|
||||
|
||||
Reference in New Issue
Block a user