Skip to content

Commit 3415f3e

Browse files
authored
Merge pull request #8 from codacy/FT-2091
Allow tools to run using a native config
2 parents 6284084 + 6bf2663 commit 3415f3e

File tree

9 files changed

+366
-107
lines changed

9 files changed

+366
-107
lines changed

build.sbt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1+
import sbt._
2+
3+
resolvers += Resolver.sonatypeRepo("releases")
4+
15
name := """codacy-engine-scala-seed"""
26

37
organization := "com.codacy"
48

5-
version := "1.5.0"
9+
version := "2.7.0"
610

7-
scalaVersion := "2.10.5"
11+
scalaVersion := "2.11.8"
812

9-
crossScalaVersions := Seq("2.10.5", "2.11.7")
13+
crossScalaVersions := Seq("2.10.5", scalaVersion.value)
1014

11-
scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-Ywarn-adapted-args", "-Xlint", "-Xfatal-warnings")
15+
scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-Ywarn-adapted-args", "-Xlint")
1216

1317
resolvers += "Bintray Typesafe Repo" at "http://dl.bintray.com/typesafe/maven-releases/"
1418

1519
libraryDependencies ++= Seq(
16-
"com.typesafe.play" %% "play-json" % "2.3.10",
17-
"org.scalatest" %% "scalatest" % "2.2.4" % "test",
18-
"com.typesafe.akka" %% "akka-actor" % "2.3.14"
20+
"com.typesafe.play" %% "play-json" % "2.4.8",
21+
"org.scalatest" %% "scalatest" % "2.2.4" % "test",
22+
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
23+
"com.codacy" %% "codacy-plugins-api" % "0.1.2" withSources(),
24+
"com.github.pathikrit" %% "better-files" % "2.14.0" withSources()
1925
)
2026

2127
organizationName := "Codacy"

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#Activator-generated Properties
22
#Thu Aug 20 15:10:54 EEST 2015
33
template.uuid=e17acfbb-1ff5-41f5-b8cf-2c40be6a8340
4-
sbt.version=0.13.8
4+
sbt.version=0.13.12
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package codacy.docker.api
2+
3+
import java.nio.file.Paths
4+
5+
import codacy.docker.api.JsonApi._
6+
import codacy.dockerApi
7+
import codacy.dockerApi.{ParameterDef, ParameterName, ParameterSpec, PatternDef, PatternId, PatternSpec, Spec, ToolName}
8+
import play.api.libs.json._
9+
10+
import scala.util.Try
11+
12+
trait BackwardsCompatability {
13+
14+
implicit class AsTool(tool: dockerApi.Tool) extends Tool {
15+
override def apply(source: Source.Directory, configuration: Option[List[Pattern.Definition]],
16+
files: Option[Set[Source.File]])(implicit specification: Tool.Specification): Try[List[Result]] = {
17+
tool.apply(
18+
path = Paths.get(source.path),
19+
conf = configuration.map(_.map(toPatternDef)),
20+
files = files.map(_.map(file => Paths.get(file.path)))
21+
)(toSpec(specification)).map(_.map(toResult))
22+
}
23+
}
24+
25+
private def toResult(result: dockerApi.Result) = {
26+
result match {
27+
case dockerApi.Issue(filename, message, patternId, line) => Result.Issue(
28+
Source.File(filename.value), Result.Message(message.value),
29+
Pattern.Id(patternId.value), Source.Line(line.value)
30+
)
31+
case dockerApi.FileError(filename, messageOpt) =>
32+
Result.FileError(Source.File(filename.value), messageOpt.map(v => ErrorMessage(v.value)))
33+
}
34+
}
35+
36+
private def toSpec(specification: Tool.Specification): Spec = {
37+
Spec(ToolName(specification.name.value), specification.patterns.map(toPatternSpec))
38+
}
39+
40+
private def toPatternSpec(specification: Pattern.Specification): PatternSpec = {
41+
PatternSpec(PatternId(specification.patternId.value), specification.parameters.map(_.map(toParameterSpec)))
42+
}
43+
44+
private def toParameterSpec(specification: Parameter.Specification): ParameterSpec = {
45+
ParameterSpec(ParameterName(specification.name.value), Json.toJson(specification.default))
46+
}
47+
48+
49+
private def toParameterDef(definition: Parameter.Definition): ParameterDef = {
50+
ParameterDef(ParameterName(definition.name.value), Json.toJson(definition.value))
51+
}
52+
53+
private def toPatternDef(definition: Pattern.Definition): PatternDef = {
54+
PatternDef(PatternId(definition.patternId.value), definition.parameters.map(_.map(toParameterDef)))
55+
}
56+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package codacy.docker.api
2+
3+
import play.api.libs.json._
4+
import scala.language.implicitConversions
5+
6+
private[api] case class ParamValue(value:JsValue) extends AnyVal with Parameter.Value
7+
8+
trait JsonApi {
9+
10+
def enumWrites[E <: Enumeration#Value]: Writes[E] = Writes((e: E) => Json.toJson(e.toString))
11+
12+
def enumReads[E <: Enumeration](e: E): Reads[e.Value] = {
13+
Reads.StringReads.flatMap { case value => Reads((_: JsValue) =>
14+
e.values.collectFirst { case enumValue if enumValue.toString == value =>
15+
JsSuccess(enumValue)
16+
}.getOrElse(JsError(s"Invalid enumeration value $value"))
17+
)
18+
}
19+
}
20+
21+
implicit def paramValueToJsValue(paramValue:Parameter.Value): JsValue = {
22+
paramValue match {
23+
case ParamValue(v) => v
24+
case _ => JsNull
25+
}
26+
}
27+
28+
implicit lazy val parameterValueFormat: Format[Parameter.Value] = Format(
29+
implicitly[Reads[JsValue]].map( Parameter.Value ),
30+
Writes( paramValueToJsValue )
31+
)
32+
33+
implicit lazy val resultLevelFormat = Format(
34+
enumReads(Result.Level),
35+
enumWrites[Result.Level]
36+
)
37+
implicit lazy val patternCategoryFormat = Format(
38+
enumReads(Pattern.Category),
39+
enumWrites[Pattern.Category]
40+
)
41+
42+
implicit lazy val patternIdFormat = Format(Reads.StringReads.map(Pattern.Id),
43+
Writes((v: Pattern.Id) => Json.toJson(v.value))) //Json.format[Pattern.Id]
44+
45+
implicit lazy val errorMessageFormat = Format(Reads.StringReads.map(ErrorMessage),
46+
Writes((v: ErrorMessage) => Json.toJson(v.value))) //Json.format[ErrorMessage]
47+
48+
implicit lazy val resultMessageFormat = Format(Reads.StringReads.map(Result.Message),
49+
Writes((v: Result.Message) => Json.toJson(v.value))) //Json.format[Result.Message]
50+
51+
implicit lazy val resultLineFormat = Format(Reads.IntReads.map(Source.Line),
52+
Writes((v: Source.Line) => Json.toJson(v.value))) //Json.format[Result.Line]
53+
54+
implicit lazy val parameterNameFormat = Format(Reads.StringReads.map(Parameter.Name),
55+
Writes((v: Parameter.Name) => Json.toJson(v.value))) //Json.format[Parameter.Name]
56+
57+
implicit lazy val toolNameFormat = Format(Reads.StringReads.map(Tool.Name),
58+
Writes((v: Tool.Name) => Json.toJson(v.value))) //Json.format[Tool.Name]
59+
60+
implicit lazy val sourceFileFormat = Format(Reads.StringReads.map(Source.File),
61+
Writes((v: Source.File) => Json.toJson(v.path))) //Json.format[Source.File]
62+
63+
implicit lazy val parameterSpecificationFormat = Json.format[Parameter.Specification]
64+
implicit lazy val parameterDefinitionFormat = Json.format[Parameter.Definition]
65+
implicit lazy val patternDefinitionFormat = Json.format[Pattern.Definition]
66+
implicit lazy val patternSpecificationFormat = Json.format[Pattern.Specification]
67+
implicit lazy val toolConfigurationFormat = Json.format[Tool.Configuration]
68+
implicit lazy val specificationFormat = Json.format[Tool.Specification]
69+
implicit lazy val configurationFormat = Json.format[Configuration]
70+
71+
implicit lazy val resultWrites: Writes[Result] = Writes[Result]((_: Result) match {
72+
case r: Result.Issue => Json.writes[Result.Issue].writes(r)
73+
case e: Result.FileError => Json.writes[Result.FileError].writes(e)
74+
})
75+
76+
implicit lazy val resultReads: Reads[Result] = {
77+
//check issue then error then oldResult
78+
issueFormat.map(identity[Result]) orElse
79+
errorFormat.map(identity[Result]) orElse
80+
oldResultReads
81+
}
82+
83+
//old formats still out there...
84+
private[this] implicit lazy val errorFormat = Json.format[Result.FileError]
85+
private[this] implicit lazy val issueFormat = Json.format[Result.Issue]
86+
87+
private[this] sealed trait ToolResult
88+
89+
private[this] case class Issue(filename: String, message: String, patternId: String, line: Int) extends ToolResult
90+
91+
private[this] case class FileError(filename: String, message: Option[String]) extends ToolResult
92+
93+
private[this] lazy val oldResultReads: Reads[Result] = {
94+
95+
lazy val IssueReadsName = Issue.getClass.getSimpleName
96+
lazy val issueReads = Json.reads[Issue].map(identity[ToolResult])
97+
98+
lazy val ErrorReadsName = FileError.getClass.getSimpleName
99+
lazy val errorReads = Json.reads[FileError].map(identity[ToolResult])
100+
101+
Reads { (result: JsValue) =>
102+
(result \ "type").validate[String].flatMap {
103+
case IssueReadsName => issueReads.reads(result)
104+
case ErrorReadsName => errorReads.reads(result)
105+
case tpe => JsError(s"not a valid result type $tpe")
106+
}
107+
}.orElse(issueReads).orElse(errorReads).map {
108+
case Issue(filename, message, patternId, line) =>
109+
Result.Issue(Source.File(filename), Result.Message(message), Pattern.Id(patternId), Source.Line(line))
110+
case FileError(filename, messageOpt) =>
111+
Result.FileError(Source.File(filename), messageOpt.map(ErrorMessage))
112+
}
113+
}
114+
}
115+
116+
object JsonApi extends JsonApi
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package codacy.docker
2+
3+
import play.api.libs.json.{JsNull, JsString, JsValue, Json}
4+
5+
import scala.util.Try
6+
7+
package object api extends JsonApi{
8+
9+
implicit class ParameterExtensions(param:Parameter.type){
10+
def Value(jsValue:JsValue):Parameter.Value = ParamValue(jsValue)
11+
def Value(raw:String):Parameter.Value = Value(Try(Json.parse(raw)).getOrElse(JsString(raw)))
12+
}
13+
14+
}

src/main/scala/codacy/dockerApi/DockerEngine.scala

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package codacy.dockerApi
22

3+
import java.nio.file.Paths
4+
35
import akka.actor.ActorSystem
6+
import codacy.docker.api.{Source, Result => NewResult}
47
import codacy.dockerApi.DockerEnvironment._
58
import play.api.libs.json.{Json, Writes}
69

710
import scala.concurrent.ExecutionContext
811
import scala.concurrent.duration._
912
import scala.util.{Failure, Success, Try}
1013

11-
abstract class DockerEngine(Tool: Tool) {
14+
abstract class DockerEngine(Tool: codacy.docker.api.Tool) {
1215

1316
lazy val sys = ActorSystem("timeoutSystem")
1417

@@ -32,49 +35,62 @@ abstract class DockerEngine(Tool: Tool) {
3235
}
3336

3437
def main(args: Array[String]): Unit = {
38+
log("starting timeout")
3539
initTimeout(timeout)
3640

37-
spec.flatMap { implicit spec =>
38-
config.flatMap { case maybeConfig =>
39-
//search for our config
40-
val maybePatterns = maybeConfig.flatMap(_.tools.collectFirst { case config if config.name == spec.name =>
41-
val allPatternIds = spec.patterns.map(_.patternId)
42-
config.patterns.filter { case pattern => allPatternIds.contains(pattern.patternId) }
43-
})
44-
val maybeFiles = maybeConfig.flatMap(_.files.map(_.map { case path =>
45-
sourcePath.resolve(path.value)
46-
}))
47-
48-
log("tool started")
49-
try {
50-
Tool.apply(
51-
path = sourcePath,
52-
conf = maybePatterns,
53-
files = maybeFiles
54-
)
55-
} catch {
56-
// We need to catch Throwable here to avoid JVM crashes
57-
// Crashes can lead to docker not exiting properly
58-
case e: Throwable =>
59-
Failure(e)
60-
}
41+
for {
42+
spec <- specification
43+
configOpt <- configuration
44+
} yield {
45+
46+
val patternsOpt = for {
47+
config <- configOpt
48+
toolCfg <- config.tools.find(_.name == spec.name)
49+
patterns <- toolCfg.patterns
50+
} yield {
51+
lazy val existingPatternIds = spec.patterns.map(_.patternId)
52+
patterns.filter(pattern => existingPatternIds contains pattern.patternId)
53+
}
54+
55+
val filesOpt = for {
56+
config <- configOpt
57+
files <- config.files
58+
} yield {
59+
//TODO: i see a problem with the .toString here, also convert it to better-files ops please!
60+
files.map { case file => file.copy(path = sourcePath.path.resolve(Paths.get(file.path)).toString) }
61+
}
62+
63+
log("tool started")
64+
// We need to catch Throwable here to avoid JVM crashes
65+
// Crashes can lead to docker not exiting properly
66+
val res = (try {
67+
Tool.apply(
68+
source = Source.Directory(sourcePath.toString()),
69+
configuration = patternsOpt,
70+
files = filesOpt
71+
)(spec)
72+
} catch {
73+
case t: Throwable =>
74+
Failure(t)
75+
})
76+
77+
res match {
78+
case Success(results) =>
79+
log("receiving results")
80+
results.foreach {
81+
case issue@NewResult.Issue(file, _, _, _) =>
82+
val relativeIssue = issue.copy(file = Source.File(relativize(issue.file.path)))
83+
logResult(relativeIssue)
84+
case error@NewResult.FileError(filename, _) =>
85+
val relativeIssue = error.copy(file = Source.File(relativize(error.file.path)))
86+
logResult(relativeIssue)
87+
}
88+
log("tool finished")
89+
System.exit(0)
90+
case Failure(error) =>
91+
error.printStackTrace(Console.err)
92+
Runtime.getRuntime.halt(1)
6193
}
62-
} match {
63-
case Success(results) =>
64-
log("receiving results")
65-
results.foreach {
66-
case issue: Issue =>
67-
val relativeIssue = issue.copy(filename = SourcePath(relativize(issue.filename.value)))
68-
logResult(relativeIssue)
69-
case error: FileError =>
70-
val relativeIssue = error.copy(filename = SourcePath(relativize(error.filename.value)))
71-
logResult(relativeIssue)
72-
}
73-
log("tool finished")
74-
System.exit(0)
75-
case Failure(error) =>
76-
error.printStackTrace(Console.err)
77-
Runtime.getRuntime.halt(1)
7894
}
7995
}
8096

0 commit comments

Comments
 (0)