我可以访问两个源文件
据此,我假设您拥有该文件的路径,我将在我的代码中将其表示为:
val pathToFile: String = ???
TL;DR
import scala.io.Source
def comments(pathToFile: String): List[String] = {
def lines: Iterator[(String, Int)] = Source.fromFile(pathToFile).getLines().zipWithIndex
val singleLineJavaDocStartAndEnds = lines.filter {
case (line, lineNumber) => line.contains("/*") && line.contains("*/")
}.map { case (line, _) => line }
val javaDocComments = lines.filter {
case (line, lineNumber) =>
(line.contains("/*") && !line.contains("*/")) ||
(!line.contains("/*") && line.contains("*/"))
}
.grouped(2).map {
case Seq((_, firstLineNumber), (_, secondLineNumber)) =>
lines
.map { case (line, _) => line }
.slice(firstLineNumber, secondLineNumber+1)
.mkString("\n")
}
val slashSlashComments = lines
.filter { case (line, _) => line.contains("//") }
.map { case (line, _) => line }
(singleLineJavaDocStartAndEnds ++ javaDocComments ++ slashSlashComments).toList
}
完整解释
首先要做的是读取文件的内容:
import scala.io.Source
def lines: Iterator[String] = Source.fromFile(pathToFile).getLines()
// here we preserve new lines, for Windows you may need to replace "\n" with "\r\n
val content: String = lines.mkString("\n")
// where `content` is the whole file as a `String`
我已将lines 设为def,以防止在多次调用lines 时出现意外结果。这是由于Source.fromFile 的返回类型以及它如何处理对文件的迭代。 This comment here adds an explanation。由于您正在读取源代码文件,我认为重新读取文件是一种安全操作,不会导致内存或性能问题。
现在我们有了文件的content,我们可以开始过滤我们不关心的行。另一种看待问题的方式是,我们只想保留 - 过滤 - 属于 cmets 的行。
编辑:
正如@jwvh 正确指出的那样,我在哪里使用.trim.startsWith 忽略了cmets,例如:
val x = 1 //mid-code-comments
/*fullLineComment*/
为了解决这个问题,我将.trim.startsWith 替换为.contains。
对于单行 cmets,这很简单:
val slashComments: Iterator[String] = lines.filter(line => line.contains("//"))
请注意上面对.trim 的调用,这很重要,因为开发人员经常启动旨在匹配代码缩进的cmets。 trim 删除字符串开头的所有空白字符。 现在使用 .contains 捕获任何从任何位置开始的注释行。
现在我们将归档多行 cmets 或 JavaDoc;例如(内容不重要):
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* .....
* .....
*/
最安全的做法是细化/* 和*/ 出现的行,并包括其间的所有行:
def lines: Iterator[(String, Int)] = Source.fromFile(pathToFile).getLines().zipWithIndex
val javaDocStartAndEnds: Iterator[(String, Int)] = lines.filter {
case (line, lineNumber) => line.contains("/*") || line.contains("*/")
}
.zipWithIndex 在每一行旁边给我们一个递增的数字。我们可以使用这些来表示源文件的行号。目前,这将为我们提供包含/* 和*/ 的行列表。我们需要将group 分成 2 个一组,因为所有这些类型的 cmets 都会有一对匹配的 /* 和 */。一旦我们有了这些组,我们就可以使用slice 选择从第一个索引到最后一个索引的所有lines。我们想要包含最后一行,所以我们对它执行+1。
val javaDocComments = javaDocStartAndEnds.grouped(2).map {
case Seq((_, firstLineNumber), (_, secondLineNumber)) =>
lines // re-calling `def lines: Iterator[(String, Int)]`
.map { case (line, _) => line } // here we only care about the `line`, not the `lineNumber`
.slice(firstLineNumber, secondLineNumber+1)
.mkString("\n")
}
最后我们可以结合slashComments和javaDocComments:
val comments: List[String] = (slashComments ++ javaDocComments).toList
无论我们加入它们的顺序如何,它们都不会出现在有序列表中。可以在此处进行的改进是在最后保留 lineNumber 和 order。
我将在顶部添加一个“太长;未阅读”(TL;DR) 版本,这样任何人都可以直接复制完整的代码,而无需逐步解释。
我怎样才能最好地做到这一点?我希望有一个从 Scala 2.11 到 Scala 3 的可维护(无需太多努力)的解决方案。
我希望我已经回答了您的问题并提供了有用的解决方案。
您提到了一个 JSON 文件作为输出。我提供的是您可以处理的内存中的List[String]。如果需要输出到 JSON,我可以用这个更新我的答案。