diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-09 00:54:45 -0500 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2017-03-09 00:54:45 -0500 |
commit | 919d7987fad82f59f998a29d79a82116049af1b9 (patch) | |
tree | a6a67d4653a5010c401124b7de2978b1f540f23b | |
parent | c45c8f241d6b2c268fd18341cf80d5467f9b50c6 (diff) | |
download | cbt-919d7987fad82f59f998a29d79a82116049af1b9.tar.gz cbt-919d7987fad82f59f998a29d79a82116049af1b9.tar.bz2 cbt-919d7987fad82f59f998a29d79a82116049af1b9.zip |
refactor scalafmt to new plugin style
turn replaceSections into helper function instead of trait method
proguard scalafmt build currently suffers from non-deterministic
formatting. Try a few times to reproduce commited Proguard.scala
-rw-r--r-- | libraries/proguard/Proguard.scala | 18 | ||||
-rw-r--r-- | libraries/proguard/build/build.scala | 72 | ||||
-rw-r--r-- | plugins/scalafmt/Scalafmt.scala | 145 | ||||
-rw-r--r-- | plugins/scalafmt/build/build.scala | 2 | ||||
-rw-r--r-- | stage2/plugins/GeneratedSections.scala | 52 |
5 files changed, 142 insertions, 147 deletions
diff --git a/libraries/proguard/Proguard.scala b/libraries/proguard/Proguard.scala index e662e0d..7d57a77 100644 --- a/libraries/proguard/Proguard.scala +++ b/libraries/proguard/Proguard.scala @@ -22,15 +22,15 @@ object KeepOptionModifier { object ProGuard { val artifactId = "proguard-base" - val groupId = "net.sf.proguard" - val version = "5.3.2" - val mainClass = "proguard.ProGuard" - val `rt.jar` = Seq( new File( System.getProperty( "java.home" ), "lib/rt.jar" ) ) + val groupId = "net.sf.proguard" + val version = "5.3.2" + val mainClass = "proguard.ProGuard" + val `rt.jar` = Seq( new File( System.getProperty( "java.home" ), "lib/rt.jar" ) ) } case class ProGuard[T]( main: Seq[String] => Int, - T: Seq[File] => T, - log: String => Unit = _ => () + T: Seq[File] => T, + log: String => Unit = _ => () ) { /** @@ -160,9 +160,9 @@ case class ProGuard[T]( private object argsFor { def apply[T: argsFor]( value: T ) = implicitly[argsFor[T]].apply( value ) implicit object SeqFile extends argsFor[Seq[File]]( v => Some( Seq( v.map( _.getPath ).mkString( ":" ) ) ) ) - implicit object File extends argsFor[File]( v => Some( Seq( v.getPath ) ) ) - implicit object String extends argsFor[String]( v => Some( Seq( v ) ) ) - implicit object Int extends argsFor[Int]( i => Some( Seq( i.toString ) ) ) + implicit object File extends argsFor[File]( v => Some( Seq( v.getPath ) ) ) + implicit object String extends argsFor[String]( v => Some( Seq( v ) ) ) + implicit object Int extends argsFor[Int]( i => Some( Seq( i.toString ) ) ) implicit object Boolean extends argsFor[Boolean]( { case false => None diff --git a/libraries/proguard/build/build.scala b/libraries/proguard/build/build.scala index 9b55ec9..c781ce2 100644 --- a/libraries/proguard/build/build.scala +++ b/libraries/proguard/build/build.scala @@ -5,7 +5,27 @@ import java.net._ import java.io._ import scala.xml._ -class Build(val context: Context) extends Scalafmt with GeneratedSections{ +class Build(val context: Context) extends Scalafmt{ + def description: String = "Type-safe scala wrapper to interfaces with ProGuard.main runner" + def inceptionYear = 2017 + + def generate = { + lib.transformFiles( sourceFiles, replaceSections( _, replacements ) ) + compile + } + + override def scalafmt = super.scalafmt.copy( + config = super.scalafmt.lib.cbtRecommendedConfig, + whiteSpaceInParenthesis = true + ) + + override def compile = { + // currently suffers from non-deterministic formatting. Try a few times to reproduce commit state. + val formatted = scalafmt.apply.map(_.string).mkString("\n") + if( formatted.nonEmpty ) System.err.println( "Formatted:\n" ++ formatted ++ "\n---------------" ) + super.compile + } + def refcard = projectDirectory / "spec/refcard.html" /** downloads html proguard parameter specification */ @@ -55,39 +75,6 @@ class Build(val context: Context) extends Scalafmt with GeneratedSections{ XmlNotDownloadingDTD.loadString( w.toString ) } - override def scalafmtConfig = { - import org.scalafmt.config._ - ScalafmtConfig.defaultWithAlign.copy( - maxColumn = 110, - continuationIndent = super.scalafmtConfig.continuationIndent.copy( - defnSite = 2 - ), - align = super.scalafmtConfig.align.copy( - tokens = AlignToken.default ++ Set( - AlignToken( ":", "Param" ), - AlignToken( "=", "Param" ) - ) + AlignToken.caseArrow, - arrowEnumeratorGenerator = true, - mixedOwners = true - ), - binPack = super.scalafmtConfig.binPack.copy( - parentConstructors = true - ), - spaces = super.scalafmtConfig.spaces.copy( - inImportCurlyBraces = true - ), - lineEndings = LineEndings.unix, - newlines = super.scalafmtConfig.newlines.copy( - penalizeSingleSelectMultiArgList = false - ), - runner = super.scalafmtConfig.runner.copy( - optimizer = super.scalafmtConfig.runner.optimizer.copy( - forceConfigStyleOnOffset = -1 - ) - ) - ) - } - /** generates Scala code from parameter specification html */ def replacements = { val tables = XML.loadFile(refcard) \\ "table" @@ -143,21 +130,4 @@ class Build(val context: Context) extends Scalafmt with GeneratedSections{ "params" -> params ) } - - override def generate{ - super.generate - compile - } - - private def whiteSpaceInParenthesis = - Seq( - "(\\(+)([^\\s\\)])".r.replaceAllIn(_:String, m => m.group(1).mkString(" ") ++ " " ++ m.group(2) ), - "([^\\s\\(])(\\)+)".r.replaceAllIn(_:String, m => m.group(1) ++ " " ++ m.group(2).mkString(" ") ) - ).reduce(_ andThen _) - - override def compile = { - scalafmt - lib.transformFiles( sourceFiles, whiteSpaceInParenthesis ) - super.compile - } } diff --git a/plugins/scalafmt/Scalafmt.scala b/plugins/scalafmt/Scalafmt.scala index 5f4e054..9d42cbd 100644 --- a/plugins/scalafmt/Scalafmt.scala +++ b/plugins/scalafmt/Scalafmt.scala @@ -8,75 +8,106 @@ import java.io.File import java.nio.file.Files._ import java.nio.file._ -/** - * This plugin provides scalafmt support for cbt. - * - */ +/** This plugin provides scalafmt support for cbt. */ trait Scalafmt extends BaseBuild { - /** - * Reformat scala source code according to `scalafmtConfig` rules - * - * @return always returns `ExitCode.Success` - */ - final def scalafmt: ExitCode = Scalafmt.format(sourceFiles, scalafmtConfig) - - /** - * Scalafmt formatting config. - * - * Tries to get style in following order: - * • project local .scalafmt.conf - * • global ~/.scalafmt.conf - * • default scalafmt config - * - * Override this task if you want to provide - * scalafmt config programmatically on your own. - */ - def scalafmtConfig: ScalafmtConfig = - Scalafmt.getStyle( - project = projectDirectory.toPath, - home = Option(System.getProperty("user.home")) map (p => Paths.get(p)) + /** Reformat scala source code according to `scalafmtConfig` rules */ + def scalafmt = { + val scalafmtLib = new ScalafmtLib(lib) + scalafmtLib.format( sourceFiles ).config( + scalafmtLib.loadConfig( + projectDirectory.toPath + ) getOrElse ScalafmtConfig.default ) + } } -object Scalafmt { +class ScalafmtLib(lib: Lib){ scalafmtLib => + def userHome = Option( System.getProperty("user.home") ).map(Paths.get(_)) - def getStyle(project: Path, home: Option[Path]): ScalafmtConfig = { - val local = getConfigPath(project) - val global = home flatMap getConfigPath - val customStyle = for { - configPath <- local.orElse(global) - style <- StyleCache.getStyleForFile(configPath.toString) - } yield style + /** Tries to load config from .scalafmt.conf in given directory or fallback directory */ + def loadConfig( directory: Path, fallback: Option[Path] = userHome ): Option[ScalafmtConfig] = { + def findIn( base: Path ): Option[Path] = { + Some( base.resolve(".scalafmt.conf").toAbsolutePath ).filter(isRegularFile(_)) + } - customStyle.getOrElse(ScalafmtConfig.default) + findIn( directory ).orElse( fallback flatMap findIn ) + .flatMap ( file => StyleCache.getStyleForFile(file.toString) ) } - def format(files: Seq[File], style: ScalafmtConfig): ExitCode = { - val results = files.filter(_.string endsWith ".scala").map(_.toPath).map{ path => - val original = new String(readAllBytes(path)) - org.scalafmt.Scalafmt.format(original, style) match { - case Formatted.Success(formatted) => - if (original != formatted) { - val tmpPath = Paths.get(path.toString ++ ".scalafmt-tmp") - write(tmpPath, formatted.getBytes) - move(tmpPath, path, StandardCopyOption.REPLACE_EXISTING) - Some(1) - } else { - Some(0) + case class format(files: Seq[File]){ + /** + * @param whiteSpaceInParenthesis more of a hack to make up for missing support in Scalafmt. Does not respect alignment and maxColumn. + */ + case class config( + config: ScalafmtConfig, + whiteSpaceInParenthesis: Boolean = false + ) extends (() => Seq[File]){ + def lib = scalafmtLib + def apply = { + val (successes, errors) = scalafmtLib.lib.transformFilesOrError( + files, + org.scalafmt.Scalafmt.format(_, config) match { + case Formatted.Success(formatted) => Right( + if( whiteSpaceInParenthesis ){ + scalafmtLib.whiteSpaceInParenthesis(formatted) + } else formatted + ) + case Formatted.Failure( e ) => Left( e ) } - case Formatted.Failure(e) => - System.err.println(s"Scalafmt failed for $path\nCause: $e\n") - None + ) + if(errors.nonEmpty) + throw new RuntimeException( + "Scalafmt failed to format some files:\n" ++ errors.map{ + case (file, error) => file.string ++ ": " ++ error.getMessage + }.mkString("\n"), + errors.head._2 + ) + successes } } - if(results.forall(_.nonEmpty)){ - System.err.println(s"Formatted ${results.flatten.sum} Scala sources") - ExitCode.Success - } else ExitCode.Failure } - private def getConfigPath(base: Path): Option[Path] = { - Some( base.resolve(".scalafmt.conf").toFile ) - .collect{ case f if f.exists && f.isFile => f.toPath.toAbsolutePath } + private def whiteSpaceInParenthesis: String => String = + Seq( + "(\\(+)([^\\s\\)])".r.replaceAllIn(_:String, m => m.group(1).mkString(" ") ++ " " ++ m.group(2) ), + "([^\\s\\(])(\\)+)".r.replaceAllIn(_:String, m => m.group(1) ++ " " ++ m.group(2).mkString(" ") ) + ).reduce(_ andThen _) + + def cbtRecommendedConfig = { + import org.scalafmt.config._ + val c = ScalafmtConfig.defaultWithAlign + c.copy( + maxColumn = 110, + continuationIndent = c.continuationIndent.copy( + defnSite = 2 + ), + align = c.align.copy( + tokens = AlignToken.default ++ Set( + // formatting for these alignment tokens seems to be deterministic right now + // Maybe because of scala meta. Killing the class loader between runs seems not to help. + // Starting a new jvm each time does. + AlignToken( "=>", ".*" ), + AlignToken( ":", ".*" ), + AlignToken( "=", ".*" ) + ) + AlignToken.caseArrow, + arrowEnumeratorGenerator = true, + mixedOwners = true + ), + binPack = c.binPack.copy( + parentConstructors = true + ), + spaces = c.spaces.copy( + inImportCurlyBraces = true + ), + lineEndings = LineEndings.unix, + newlines = c.newlines.copy( + penalizeSingleSelectMultiArgList = false + ), + runner = c.runner.copy( + optimizer = c.runner.optimizer.copy( + forceConfigStyleOnOffset = -1 + ) + ) + ) } } diff --git a/plugins/scalafmt/build/build.scala b/plugins/scalafmt/build/build.scala index b37d769..e592873 100644 --- a/plugins/scalafmt/build/build.scala +++ b/plugins/scalafmt/build/build.scala @@ -1,7 +1,7 @@ import cbt._ class Build(val context: Context) extends Plugin { - private val ScalafmtVersion = "0.5.7" + private val ScalafmtVersion = "0.6.2" override def dependencies = super.dependencies ++ diff --git a/stage2/plugins/GeneratedSections.scala b/stage2/plugins/GeneratedSections.scala index dc065cf..aedcaff 100644 --- a/stage2/plugins/GeneratedSections.scala +++ b/stage2/plugins/GeneratedSections.scala @@ -1,33 +1,27 @@ package cbt import java.nio.file.Files._ -trait GeneratedSections extends BaseBuild{ - def generatedSectionStartMarker( name: String ) = s"AUTO GENERATED SECTION BEGIN: $name " - def generatedSectionEndMarker( name: String ) = s"AUTO GENERATED SECTION END: $name " - assert( - generatedSectionStartMarker("foo").endsWith(" "), - "generatedSectionStartMarker needs to end with a space character" - ) - assert( - generatedSectionEndMarker("foo").endsWith(" "), - "generatedSectionEndMarker needs to end with a space character" - ) - - def replacements: Seq[(String, String)] - - def generate = { - def replaceSections(subject: String, sections: Seq[(String, String)]): String = { - sections.headOption.map{ - case (name, replacement) => - replaceSections( - s"(?s)(\n[^\n]*AUTO GENERATED SECTION BEGIN: $name [^\n]*\n).*(\n[^\n]*AUTO GENERATED SECTION END: $name [^\n]*\n)" - .r.replaceAllIn( subject, m => m.group(1) ++ replacement ++ m.group(2) ), - sections.tail - ) - }.getOrElse(subject) - } - - val updated = lib.transformFiles( sourceFiles, replaceSections( _, replacements ) ) - - logger.log("generated-sections","Updated:" + updated.map(_ ++ "\n").mkString) +object replaceSections{ + def apply( + subject: String, + sections: Seq[(String, String)], + generatedSectionBeginMarker: String => String = name => s"AUTO GENERATED SECTION BEGIN: $name ", + generatedSectionEndMarker: String => String = name => s"AUTO GENERATED SECTION END: $name " + ): String = { + assert( + generatedSectionBeginMarker("foo").endsWith(" "), + "generatedSectionStartMarker needs to end with a space character" + ) + assert( + generatedSectionEndMarker("foo").endsWith(" "), + "generatedSectionEndMarker needs to end with a space character" + ) + sections.headOption.map{ + case (name, replacement) => + replaceSections( + s"(?s)(\n[^\n]*AUTO GENERATED SECTION BEGIN: $name [^\n]*\n).*(\n[^\n]*AUTO GENERATED SECTION END: $name [^\n]*\n)" + .r.replaceAllIn( subject, m => m.group(1) ++ replacement ++ m.group(2) ), + sections.tail + ) + }.getOrElse(subject) } } |