22
22
import java .io .Serializable ;
23
23
import java .nio .charset .Charset ;
24
24
import java .nio .file .Files ;
25
+ import java .nio .file .Paths ;
25
26
import java .util .ArrayList ;
26
27
import java .util .Arrays ;
27
28
import java .util .Collection ;
37
38
import org .gradle .api .Action ;
38
39
import org .gradle .api .GradleException ;
39
40
import org .gradle .api .Project ;
41
+ import org .gradle .api .artifacts .repositories .MavenArtifactRepository ;
40
42
import org .gradle .api .file .ConfigurableFileTree ;
41
43
import org .gradle .api .file .FileCollection ;
42
44
import org .gradle .api .plugins .BasePlugin ;
63
65
import com .diffplug .spotless .generic .TrimTrailingWhitespaceStep ;
64
66
import com .diffplug .spotless .npm .NpmPathResolver ;
65
67
import com .diffplug .spotless .npm .PrettierFormatterStep ;
68
+ import com .diffplug .spotless .rome .RomeStep ;
66
69
67
70
import groovy .lang .Closure ;
68
71
@@ -648,6 +651,296 @@ protected FormatterStep createStep() {
648
651
this .prettierConfig ));
649
652
}
650
653
}
654
+
655
+ public abstract static class RomeStepConfig <Self extends RomeStepConfig <Self >> {
656
+ /**
657
+ * Optional path to the directory with configuration file for Rome. The file
658
+ * must be named {@code rome.json}. When none is given, the default
659
+ * configuration is used. If this is a relative path, it is resolved against the
660
+ * project's base directory.
661
+ */
662
+ @ Nullable
663
+ private Object configPath ;
664
+
665
+ /**
666
+ * Optional directory where the downloaded Rome executable is placed. If this is
667
+ * a relative path, it is resolved against the project's base directory.
668
+ * Defaults to
669
+ * <code>~/.m2/repository/com/diffplug/spotless/spotless-data/rome</code>.
670
+ */
671
+ @ Nullable
672
+ private Object downloadDir ;
673
+
674
+ /**
675
+ * Optional path to the Rome executable. Either a <code>version</code> or a
676
+ * <code>pathToExe</code> should be specified. When not given, an attempt is
677
+ * made to download the executable for the given version from the network. When
678
+ * given, the executable is used and the <code>version</code> parameter is
679
+ * ignored.
680
+ * <p>
681
+ * When an absolute path is given, that path is used as-is. When a relative path
682
+ * is given, it is resolved against the project's base directory. When only a
683
+ * file name (i.e. without any slashes or back slash path separators such as
684
+ * {@code rome}) is given, this is interpreted as the name of a command with
685
+ * executable that is in your {@code path} environment variable. Use
686
+ * {@code ./executable-name} if you want to use an executable in the project's
687
+ * base directory.
688
+ */
689
+ @ Nullable
690
+ private Object pathToExe ;
691
+
692
+ /** A reference to the Gradle project for which spotless is executed. */
693
+ private final Project project ;
694
+
695
+ /** Replaces the current Rome formatter step with the given step. */
696
+ private final Consumer <FormatterStep > replaceStep ;
697
+
698
+ /**
699
+ * Rome version to download, applies only when no <code>pathToExe</code> is
700
+ * specified explicitly. Either a <code>version</code> or a
701
+ * <code>pathToExe</code> should be specified. When not given, a default known
702
+ * version is used. For stable builds, it is recommended that you always set the
703
+ * version explicitly. This parameter is ignored when you specify a
704
+ * <code>pathToExe</code> explicitly.
705
+ */
706
+ @ Nullable
707
+ private String version ;
708
+
709
+ protected RomeStepConfig (Project project , Consumer <FormatterStep > replaceStep , String version ) {
710
+ this .project = requireNonNull (project );
711
+ this .replaceStep = requireNonNull (replaceStep );
712
+ this .version = version ;
713
+ }
714
+
715
+ /**
716
+ * Optional path to the directory with configuration file for Rome. The file
717
+ * must be named {@code rome.json}. When none is given, the default
718
+ * configuration is used. If this is a relative path, it is resolved against the
719
+ * project's base directory.
720
+ * @return This step for further configuration.
721
+ */
722
+ public Self configPath (Object configPath ) {
723
+ this .configPath = configPath ;
724
+ replaceStep ();
725
+ return getThis ();
726
+ }
727
+
728
+ /**
729
+ * Optional directory where the downloaded Rome executable is placed. If this is
730
+ * a relative path, it is resolved against the project's base directory.
731
+ * Defaults to
732
+ * <code>~/.m2/repository/com/diffplug/spotless/spotless-data/rome</code>.
733
+ * @return This step for further configuration.
734
+ */
735
+ public Self downloadDir (Object downloadDir ) {
736
+ this .downloadDir = downloadDir ;
737
+ replaceStep ();
738
+ return getThis ();
739
+ }
740
+
741
+ /**
742
+ * Optional path to the Rome executable. Overwrites the configured version. No
743
+ * attempt is made to download the Rome executable from the network.
744
+ * <p>
745
+ * When an absolute path is given, that path is used as-is. When a relative path
746
+ * is given, it is resolved against the project's base directory. When only a
747
+ * file name (i.e. without any slashes or back slash path separators such as
748
+ * {@code rome}) is given, this is interpreted as the name of a command with
749
+ * executable that is in your {@code path} environment variable. Use
750
+ * {@code ./executable-name} if you want to use an executable in the project's
751
+ * base directory.
752
+ * @return This step for further configuration.
753
+ */
754
+ public Self pathToExe (Object pathToExe ) {
755
+ this .pathToExe = pathToExe ;
756
+ replaceStep ();
757
+ return getThis ();
758
+ }
759
+
760
+ /**
761
+ * Creates a new formatter step that formats code by calling the Rome
762
+ * executable, using the current configuration.
763
+ *
764
+ * @return A new formatter step for the Rome formatter.
765
+ */
766
+ protected FormatterStep createStep () {
767
+ var builder = newBuilder ();
768
+ if (configPath != null ) {
769
+ var resolvedConfigPath = project .file (configPath );
770
+ builder .withConfigPath (resolvedConfigPath .toString ());
771
+ }
772
+ builder .withLanguage (getLanguage ());
773
+ var rome = builder .build ();
774
+ return rome .create ();
775
+ }
776
+
777
+ /**
778
+ * Gets the language (syntax) of the input files to format. When
779
+ * <code>null</code> or the empty string, the language is detected automatically
780
+ * from the file name. Currently the following languages are supported by Rome:
781
+ * <ul>
782
+ * <li>js (JavaScript)</li>
783
+ * <li>jsx (JavaScript + JSX)</li>
784
+ * <li>js? (JavaScript or JavaScript + JSX, depending on the file
785
+ * extension)</li>
786
+ * <li>ts (TypeScript)</li>
787
+ * <li>tsx (TypeScript + JSX)</li>
788
+ * <li>ts? (TypeScript or TypeScript + JSX, depending on the file
789
+ * extension)</li>
790
+ * <li>json (JSON)</li>
791
+ * </ul>
792
+ *
793
+ * @return The language of the input files.
794
+ */
795
+ protected abstract String getLanguage ();
796
+
797
+ /**
798
+ * @return This Rome config instance.
799
+ */
800
+ protected abstract Self getThis ();
801
+
802
+ /**
803
+ * Creates a new Rome step and replaces the existing Rome step in the list of
804
+ * format steps.
805
+ */
806
+ protected void replaceStep () {
807
+ replaceStep .accept (createStep ());
808
+ }
809
+
810
+ /**
811
+ * Finds the data directory that can be used for storing shared data such as
812
+ * Rome executable globally. This is a directory in the local repository, e.g.
813
+ * <code>~/.m2/repository/com/diffplus/spotless/spotless-data<code>.
814
+ *
815
+ * @return The directory for storing shared data.
816
+ */
817
+ private File findDataDir () {
818
+ var currentRepo = project .getRepositories ().stream ()
819
+ .filter (r -> r instanceof MavenArtifactRepository )
820
+ .map (r -> (MavenArtifactRepository ) r )
821
+ .filter (r -> "file" .equals (r .getUrl ().getScheme ()))
822
+ .findAny ().orElse (null );
823
+ // Temporarily add mavenLocal() repository to get its file URL
824
+ var localRepo = currentRepo != null ? (MavenArtifactRepository )currentRepo : project .getRepositories ().mavenLocal ();
825
+ try {
826
+ // e.g. ~/.m2/repository/
827
+ var repoPath = Paths .get (localRepo .getUrl ().getPath ());
828
+ var dataPath = repoPath .resolve ("com" ).resolve ("diffplus" ).resolve ("spotless" ).resolve ("spotless-data" );
829
+ return dataPath .toAbsolutePath ().toFile ();
830
+ }
831
+ finally {
832
+ // Remove mavenLocal() repository again if it was not part of the project
833
+ if (currentRepo == null ) {
834
+ project .getRepositories ().remove (localRepo );
835
+ }
836
+ }
837
+ }
838
+
839
+ /**
840
+ * A new builder for configuring a Rome step that either downloads the Rome
841
+ * executable with the given version from the network, or uses the executable
842
+ * from the given path.
843
+ *
844
+ * @return A builder for a Rome step.
845
+ */
846
+ private RomeStep .Builder newBuilder () {
847
+ if (pathToExe != null ) {
848
+ var resolvedPathToExe = resolvePathToExe ();
849
+ return RomeStep .withExePath (resolvedPathToExe );
850
+ } else {
851
+ var downloadDir = resolveDownloadDir ();
852
+ return RomeStep .withExeDownload (version , downloadDir );
853
+ }
854
+ }
855
+
856
+ /**
857
+ * Resolves the path to the Rome executable. When the path is only a file name,
858
+ * do not perform any resolution and interpret it as a command that must be on
859
+ * the user's path. Otherwise resolve the executable path against the project's
860
+ * base directory.
861
+ *
862
+ * @param config Configuration from the Maven Mojo execution with details about
863
+ * the currently executed project.
864
+ * @return The resolved path to the Rome executable.
865
+ */
866
+ private String resolvePathToExe () {
867
+ var fileNameOnly = pathToExe instanceof String && Paths .get (pathToExe .toString ()).getNameCount () == 1 ;
868
+ if (fileNameOnly ) {
869
+ return pathToExe .toString ();
870
+ } else {
871
+ return project .file (pathToExe ).toString ();
872
+ }
873
+ }
874
+
875
+ /**
876
+ * Resolves the directory to use for storing downloaded Rome executable. When a
877
+ * {@link #downloadDir} is given, use that directory, resolved against the
878
+ * current project's directory. Otherwise, use the {@code Rome} sub folder in
879
+ * the shared data directory.
880
+ *
881
+ * @param config Configuration for this step.
882
+ * @return The download directory for the Rome executable.
883
+ */
884
+ private String resolveDownloadDir () {
885
+ if (downloadDir != null ) {
886
+ return project .file (downloadDir ).toString ();
887
+ } else {
888
+ return findDataDir ().toPath ().resolve ("rome" ).toString ();
889
+ }
890
+ }
891
+ }
892
+
893
+ /**
894
+ * Generic Rome formatter step that detects the language of the input file from
895
+ * the file name. It should be specified as a formatter step for a generic
896
+ * <code>format{ ... }</code>.
897
+ */
898
+ public class RomeGeneric extends RomeStepConfig <RomeGeneric > {
899
+ @ Nullable
900
+ String language ;
901
+
902
+ /**
903
+ * Creates a new Rome config that downloads the Rome executable for the given version from the network.
904
+ * @param version Rome version to use. The default version is used when <code>null</code>.
905
+ */
906
+ public RomeGeneric (String version ) {
907
+ super (getProject (), FormatExtension .this ::replaceStep , version );
908
+ }
909
+
910
+ /**
911
+ * Sets the language (syntax) of the input files to format. When
912
+ * <code>null</code> or the empty string, the language is detected automatically
913
+ * from the file name. Currently the following languages are supported by Rome:
914
+ * <ul>
915
+ * <li>js (JavaScript)</li>
916
+ * <li>jsx (JavaScript + JSX)</li>
917
+ * <li>js? (JavaScript or JavaScript + JSX, depending on the file
918
+ * extension)</li>
919
+ * <li>ts (TypeScript)</li>
920
+ * <li>tsx (TypeScript + JSX)</li>
921
+ * <li>ts? (TypeScript or TypeScript + JSX, depending on the file
922
+ * extension)</li>
923
+ * <li>json (JSON)</li>
924
+ * </ul>
925
+ * @param language The language of the files to format.
926
+ * @return This step for further configuration.
927
+ */
928
+ public RomeGeneric language (String language ) {
929
+ this .language = language ;
930
+ replaceStep ();
931
+ return this ;
932
+ }
933
+
934
+ @ Override
935
+ protected String getLanguage () {
936
+ return language ;
937
+ }
938
+
939
+ @ Override
940
+ protected RomeGeneric getThis () {
941
+ return this ;
942
+ }
943
+ }
651
944
652
945
/** Uses the default version of prettier. */
653
946
public PrettierConfig prettier () {
@@ -665,6 +958,22 @@ public PrettierConfig prettier(Map<String, String> devDependencies) {
665
958
addStep (prettierConfig .createStep ());
666
959
return prettierConfig ;
667
960
}
961
+
962
+ /**
963
+ * Defaults to downloading the default Rome version from the network. To work
964
+ * offline, you can specify the path to the Rome executable via
965
+ * {@code rome().pathToExe(...)}.
966
+ */
967
+ public RomeGeneric rome () {
968
+ return rome (null );
969
+ }
970
+
971
+ /** Downloads the given Rome version from the network. */
972
+ public RomeGeneric rome (String version ) {
973
+ var romeConfig = new RomeGeneric (version );
974
+ addStep (romeConfig .createStep ());
975
+ return romeConfig ;
976
+ }
668
977
669
978
/** Uses the default version of clang-format. */
670
979
public ClangFormatConfig clangFormat () {
0 commit comments