rewrite icon indicating copy to clipboard operation
rewrite copied to clipboard

Allow usage of additional file extensions per parser.

Open ammachado opened this issue 1 year ago • 9 comments

What's changed?

Allow parsers to parse additional file extensions.

What's your motivation?

Parse Karaf's configuration files as properties.

Anything in particular you'd like reviewers to focus on?

No

Anyone you would like to review specifically?

No

Have you considered any alternatives or workarounds?

No

Any additional context

No

Checklist

  • [x] I've added unit tests to cover both positive and negative cases
  • [x] I've read and applied the recipe conventions and best practices
  • [x] I've used the IntelliJ IDEA auto-formatter on affected files

ammachado avatar Feb 12 '24 03:02 ammachado

I like the idea! But I wonder if the execution context shouldn't instead allow registering alternative implementations for accept(), so that file extensions can also be removed (let's say someone doesn't want to parse Jenkinsfile files anymore). This would require some further refactoring, so that an alternative implementation can still optionally delegate to the default implementation. Thoughts on this?

knutwannheden avatar Feb 12 '24 20:02 knutwannheden

Perhaps if we just add a file exclusion pattern, similar to the additionalExtensions implemented here, would work? ... accept(..) would check for the default extensions, then inclusions and exclusions.

ammachado avatar Feb 14 '24 20:02 ammachado

It isn't clear to me that we need to make this extensible. We're happy to add to the list of extensions for each parser. Do you have a file extension you'd like to accrue to one of the parsers by default? Do you have a scenario where hard-coding is insufficient or otherwise impossible?

sambsnyd avatar Apr 02 '24 00:04 sambsnyd

This PR adds the option to include/exclude file patterns per parser. In my original use case, I have properties files that have .cfg extension instead of .properties, and I want to use the existing property recipes to refactor them.

ammachado avatar Apr 04 '24 14:04 ammachado

I also described my use case in #4062. I have a yaml file without extension. To parse this as YAML required me to not only subclass YamlParser and a few other types, but I had to pass the file pattern as plain text matchers to the Maven plugin. That functionally has been deprecated, so it's not clear to me how I'd go about targeting my desired file mappings without this PR.

jimschubert avatar May 01 '24 22:05 jimschubert

I would rather add an entry to the hardcoded list than add an entire extension mechanism. If all you need is to parse Karaf's configuration files as properties we can add it to the list of extensions that parser accepts by default.

sambsnyd avatar Jun 13 '24 16:06 sambsnyd

Hard coding my company's internal tooling names into OpenRewrite isn't an acceptable solution.

jimschubert avatar Jun 14 '24 16:06 jimschubert

@jimschubert Please note the plaintextMasks option is not deprecated; we only deprecated a duplicate option without a qualifying prefix.

To offer an alternative that's already possible using existing mechanisms, one could try a recipe that runs first to convert any PlainText parsed files into Yaml with a recipe; this is something that's come up on conversation before internally.

Show recipe that converts a plain text parsed file into Yaml

Note that I haven't checked the below code; just meant to illustrate the general approach.

package org.openrewrite.yaml;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.text.PlainText;
import org.openrewrite.text.PlainTextVisitor;

import java.util.Optional;

@Value
@EqualsAndHashCode(callSuper = false)
public class AsYaml extends Recipe {

    @Option(displayName = "File pattern",
            description = "Ant-style path pattern matching source files to parse as YAML.",
            example = "com/myapp/**/*.acme")
    String filePattern;

    @Override
    public String getDisplayName() {
        return "Parse as YAML";
    }

    @Override
    public String getDescription() {
        return "Parse an existing PlainText file as YAML. Useful for when you have a file that is not recognized as YAML, but you know it is.";
    }

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor() {
        PlainTextVisitor<ExecutionContext> plainTextVisitor = new PlainTextVisitor<ExecutionContext>() {
            @Override
            public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
                if (tree instanceof PlainText) {
                    PlainText pt = (PlainText) tree;
                    Optional<SourceFile> first = YamlParser.builder().build()
                            .parse(tree.print(getCursor()))
                            .findFirst();
                    if (first.isPresent()) {
                        SourceFile sourceFile = first.get();
                        if (pt.getCharset() != null) {
                            sourceFile = sourceFile.withCharset(pt.getCharset());
                        }
                        return sourceFile
                                .withCharsetBomMarked(pt.isCharsetBomMarked())
                                .withChecksum(pt.getChecksum())
                                .withFileAttributes(pt.getFileAttributes())
                                .withSourcePath(pt.getSourcePath())
                                .withMarkers(pt.getMarkers())
                                .withId(pt.getId());
                    }
                }
                return tree;
            }
        };
        return Preconditions.check(new FindSourceFiles(filePattern), plainTextVisitor);
    }
}

If the above recipe approach is workable then that might then remove the need for an extension mechanism, and the necessary configuration handling downstream to do that well.

timtebeek avatar Jul 14 '24 14:07 timtebeek

Thanks @timtebeek for the clarification on the depreciation and the example code! The depreciation message didn't make the clarification you made above, and although I checked the code, I must have missed that a prefixed option was still available.

I'll give your example recipe a try. This is effectively what I'm doing already, as I found that I had to first do a search or other recipe on the file to convert. However I had to put a hack around the parser due to hard-coded extensions: https://github.com/openrewrite/rewrite/blob/98b720b802ea9d4f3d5b6c3e0ce532520c3e311f/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java#L436. I can't recall what was calling the accept method.

I'll return to work around the beginning of August and respond to the issue if my experience with the YAML files has changed after incorporating the above.

jimschubert avatar Jul 27 '24 03:07 jimschubert