#scala #javadoc #scaladoc #doclet
Вопрос:
Я хотел бы программно извлечь комментарии к коду из исходного файла Scala.
У меня есть доступ как к исходному файлу, так и к объектам классов, чьи комментарии меня интересуют. Я также открыт для написания комментариев в исходном файле Scala в определенной форме, чтобы облегчить извлечение (хотя все еще придерживаюсь соглашений Scaladoc).
В частности, я не ищу HTML или аналогичные выходные данные.
Тем не менее, объект json, который я затем могу просмотреть, чтобы получить комментарии для каждого поля, был бы совершенно нормальным (хотя json не является обязательным требованием). В идеале я хотел бы получить комментарий класса или члена класса с указанием его «полного» имени или объекта класса.
Как мне лучше всего это сделать? Я надеюсь на решение, которое можно поддерживать (без особых усилий) от Scala 2.11 до Scala 3.
Ценю любую помощь!
Ответ №1:
У меня есть доступ к обоим исходным файлам
Исходя из этого, я предполагаю, что у вас есть путь к файлу, который я представлю в своем коде как:
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("/*") amp;amp; line.contains("*/")
}.map { case (line, _) => line }
val javaDocComments = lines.filter {
case (line, lineNumber) =>
(line.contains("/*") amp;amp; !line.contains("*/")) ||
(!line.contains("/*") amp;amp; 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 "rn
val content: String = lines.mkString("n")
// where `content` is the whole file as a `String`
Я сделал lines
def
это, чтобы предотвратить непреднамеренные результаты при lines
многократном вызове. Это связано с типом возвращаемого Source.fromFile
значения и тем, как он обрабатывает итерацию по файлу. Этот комментарий здесь добавляет объяснение. Поскольку вы читаете файлы исходного кода, я думаю, что перечитывание файла является безопасной операцией и не приведет к проблемам с памятью или производительностью.
Теперь, когда у нас есть content
файл, мы можем начать отфильтровывать строки, которые нас не волнуют. Другой способ рассмотрения проблемы заключается в том, что мы хотим фильтровать только те строки, которые являются комментариями.
Редактировать:
Как справедливо отметил @jwvh, где я использовал .trim.startsWith
игнорируемые комментарии, такие как:
val x = 1 //mid-code-comments
/*fullLineComment*/
Чтобы решить эту проблему, я заменил .trim.startsWith
ее на .contains
.
Для однострочных комментариев это просто:
val slashComments: Iterator[String] = lines.filter(line => line.contains("//"))
Обратите внимание на вызов Теперь с помощью .trim
выше, который важен, так как часто разработчики запускают комментарии, предназначенные для соответствия отступу кода. trim
удаляет все пробелы в начале строки. .contains
которого ловится любая строка с комментарием, начинающаяся в любом месте.
Теперь мы подадим многострочные комментарии или 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, так как все эти типы комментариев будут иметь совпадающую пару /*
и */
. Как только у нас появятся эти группы, мы сможем выбрать , используя 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
и упорядочить это в конце.
Я включу версию «слишком долго; не читал» (TL;DR) вверху, чтобы любой мог просто скопировать код полностью без пошагового объяснения.
Как мне лучше всего это сделать? Я надеюсь на решение, которое можно поддерживать (без особых усилий) от Scala 2.11 до Scala 3.
Надеюсь, я ответил на ваш вопрос и предложил полезное решение. Вы упомянули файл JSON в качестве вывода. То, что я предоставил, находится List[String]
в памяти, которую вы можете обработать. Если требуется вывод в JSON, я могу обновить свой ответ этим.
Комментарии:
1. Все
val x = 1 //mid-code-comments
они пропущены, и это/*fullLineComment*/
сбросит ваш многострочный расчет.2. Привет @jwvh. Это совершенно верно! Я обновлю свой ответ, чтобы заменить
.trim.startsWith
его наcontains
.3. @skm Пожалуйста, дайте мне знать, как вы пробуете это решение 🙂
4. Вам действительно следует протестировать свой код перед публикацией. Текущая итерация завершается неудачно для нескольких комбинаций кода/комментария. Кроме того, совсем не ясно, что (теперь молчащая) операция хочет простой синтаксический анализатор текста. Это дело о «данном … объект класса» в лучшем случае сбивает с толку.
5. Спасибо @jvwh. Вы, безусловно, правы. Я вижу
/*fullLineComment*/
, что в последней версии это не было обработано. Не могли бы вы привести примеры любых других «комбинаций кода/комментариев» , и я обновлю их. Я намерен сделать пример как можно более простым и поэтому намерен рассматривать только самые распространенные случаи. Некоторые вещи все еще проскальзывают через сеть, например URL-адреса , содержащие a//
, и будут появляться.