Skip to content

Commit 1be0e6d

Browse files
authored
Merge pull request #1598 from Gedochao/fix-md-using-directives
Fix `using` directives for Markdown inputs
2 parents 7ee0b52 + 0bb41be commit 1be0e6d

File tree

12 files changed

+628
-170
lines changed

12 files changed

+628
-170
lines changed

modules/build/src/main/scala/scala/build/internal/markdown/MarkdownCodeBlock.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ object MarkdownCodeBlock {
6161
def findCodeBlocks(
6262
subPath: os.SubPath,
6363
md: String,
64-
maybeRecoverOnError: BuildException => Option[BuildException]
64+
maybeRecoverOnError: BuildException => Option[BuildException] = Some(_)
6565
): Either[BuildException, Seq[MarkdownCodeBlock]] = {
6666
val allLines = md
6767
.lines()

modules/build/src/main/scala/scala/build/internal/markdown/MarkdownCodeWrapper.scala

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,47 @@
11
package scala.build.internal.markdown
22

33
import scala.annotation.tailrec
4-
import scala.build.EitherCps.{either, value}
54
import scala.build.errors.BuildException
65
import scala.build.internal.markdown.MarkdownCodeBlock
76
import scala.build.internal.{AmmUtil, Name}
7+
import scala.build.preprocessing.{
8+
ExtractedDirectives,
9+
PreprocessedMarkdown,
10+
PreprocessedMarkdownCodeBlocks
11+
}
812

913
/** A util for extraction and wrapping of code blocks in Markdown files.
1014
*/
1115
object MarkdownCodeWrapper {
1216

17+
case class WrappedMarkdownCode(
18+
code: String,
19+
directives: ExtractedDirectives = ExtractedDirectives.empty
20+
)
21+
1322
/** Extracts scala code blocks from Markdown snippets, divides them into 3 categories and wraps
1423
* when necessary.
1524
*
1625
* @param subPath
1726
* the project [[os.SubPath]] to the Markdown file
18-
* @param content
19-
* Markdown code
27+
* @param markdown
28+
* preprocessed Markdown code blocks
2029
* @return
21-
* a tuple of (Option(simple scala snippets code), Option(raw scala snippets code), Option(test
22-
* scala snippets code))
30+
* a tuple of (Option(simple scala code blocks), Option(raw scala snippets code blocks),
31+
* Option(test scala snippets code blocks))
2332
*/
2433
def apply(
2534
subPath: os.SubPath,
26-
content: String,
27-
maybeRecoverOnError: BuildException => Option[BuildException] = b => Some(b)
28-
): Either[BuildException, (Option[String], Option[String], Option[String])] = either {
35+
markdown: PreprocessedMarkdown
36+
): (Option[WrappedMarkdownCode], Option[WrappedMarkdownCode], Option[WrappedMarkdownCode]) = {
2937
val (pkg, wrapper) = AmmUtil.pathToPackageWrapper(subPath)
3038
val maybePkgString =
3139
if pkg.isEmpty then None else Some(s"package ${AmmUtil.encodeScalaSourcePath(pkg)}")
32-
val allSnippets = value(MarkdownCodeBlock.findCodeBlocks(subPath, content, maybeRecoverOnError))
33-
val (rawSnippets, processedSnippets) = allSnippets.partition(_.isRaw)
34-
val (testSnippets, mainSnippets) = processedSnippets.partition(_.isTest)
35-
val wrapperName = s"${wrapper.raw}_md"
40+
val wrapperName = s"${wrapper.raw}_md"
3641
(
37-
wrapScalaCode(mainSnippets, wrapperName, maybePkgString),
38-
rawScalaCode(rawSnippets),
39-
rawScalaCode(testSnippets)
42+
wrapScalaCode(markdown.scriptCodeBlocks, wrapperName, maybePkgString),
43+
rawScalaCode(markdown.rawCodeBlocks),
44+
rawScalaCode(markdown.testCodeBlocks)
4045
)
4146
}
4247

@@ -75,20 +80,20 @@ object MarkdownCodeWrapper {
7580
* an option of the wrapped code String
7681
*/
7782
def wrapScalaCode(
78-
snippets: Seq[MarkdownCodeBlock],
83+
preprocessed: PreprocessedMarkdownCodeBlocks,
7984
wrapperName: String,
8085
pkg: Option[String]
81-
): Option[String] =
86+
): Option[WrappedMarkdownCode] =
8287
code(
83-
snippets,
88+
preprocessed.codeBlocks,
8489
s => {
8590
val packageDirective = pkg.map(_ + "; ").getOrElse("")
8691
val noWarnAnnotation = """@annotation.nowarn("msg=pure expression does nothing")"""
8792
val firstLine =
8893
s"""${packageDirective}object $wrapperName { $noWarnAnnotation def main(args: Array[String]): Unit = { """
8994
s.indices.foldLeft(0 -> firstLine) {
9095
case ((nextScopeIndex, sum), index) =>
91-
if snippets(index).resetScope || index == 0 then
96+
if preprocessed.codeBlocks(index).resetScope || index == 0 then
9297
nextScopeIndex + 1 -> (sum :++ s"${scopeObjectName(nextScopeIndex)}; ")
9398
else nextScopeIndex -> sum // that class hasn't been created
9499
}
@@ -97,7 +102,7 @@ object MarkdownCodeWrapper {
97102
.:++(generateMainScalaLines(s, 0, 0, 0))
98103
.:++("}")
99104
}
100-
)
105+
).map(c => WrappedMarkdownCode(c, preprocessed.extractedDirectives))
101106

102107
@tailrec
103108
private def generateMainScalaLines(
@@ -137,8 +142,9 @@ object MarkdownCodeWrapper {
137142
* @return
138143
* an option of the resulting code String
139144
*/
140-
def rawScalaCode(snippets: Seq[MarkdownCodeBlock]): Option[String] =
141-
code(snippets, generateRawScalaLines(_, 0, 0))
145+
def rawScalaCode(preprocessed: PreprocessedMarkdownCodeBlocks): Option[WrappedMarkdownCode] =
146+
code(preprocessed.codeBlocks, generateRawScalaLines(_, 0, 0))
147+
.map(c => WrappedMarkdownCode(c, preprocessed.extractedDirectives))
142148

143149
@tailrec
144150
private def generateRawScalaLines(

modules/build/src/main/scala/scala/build/internal/markdown/MarkdownOpenFence.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ case class MarkdownOpenFence(
3939
val bodyLines: Array[String] = lines.slice(start, tickEndLine)
4040
MarkdownCodeBlock(
4141
info.split("\\s+").toList, // strip info by whitespaces
42-
bodyLines.tail.foldLeft(bodyLines.head)((body, line) => body.:++("\n" + line)),
42+
bodyLines.mkString("\n"),
4343
start, // snippet has to begin in the new line
4444
tickEndLine - 1 // ending backticks have to be placed below the snippet
4545
)

modules/build/src/main/scala/scala/build/preprocessing/ExtractedDirectives.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.virtuslab.using_directives.custom.model.{
99
import com.virtuslab.using_directives.custom.utils.ast.{UsingDef, UsingDefs}
1010
import com.virtuslab.using_directives.{Context, UsingDirectivesProcessor}
1111

12+
import scala.annotation.targetName
1213
import scala.build.errors.*
1314
import scala.build.preprocessing.directives.{DirectiveUtil, ScopedDirective, StrictDirective}
1415
import scala.build.{Logger, Position}
@@ -18,7 +19,11 @@ import scala.jdk.CollectionConverters.*
1819
case class ExtractedDirectives(
1920
offset: Int,
2021
directives: Seq[StrictDirective]
21-
)
22+
) {
23+
@targetName("append")
24+
def ++(other: ExtractedDirectives): ExtractedDirectives =
25+
ExtractedDirectives(offset, directives ++ other.directives)
26+
}
2227

2328
object ExtractedDirectives {
2429

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package scala.build.preprocessing
2+
3+
import com.virtuslab.using_directives.custom.model.UsingDirectiveKind
4+
5+
import scala.build.EitherCps.{either, value}
6+
import scala.build.Logger
7+
import scala.build.errors.BuildException
8+
import scala.build.internal.markdown.MarkdownCodeBlock
9+
10+
object MarkdownCodeBlockProcessor {
11+
def process(
12+
codeBlocks: Seq[MarkdownCodeBlock],
13+
reportingPath: Either[String, os.Path],
14+
scopePath: ScopePath,
15+
logger: Logger,
16+
maybeRecoverOnError: BuildException => Option[BuildException]
17+
): Either[BuildException, PreprocessedMarkdown] = either {
18+
val (rawCodeBlocks, remaining) = codeBlocks.partition(_.isRaw)
19+
val (testCodeBlocks, scriptCodeBlocks) = remaining.partition(_.isTest)
20+
def preprocessCodeBlocks(cbs: Seq[MarkdownCodeBlock])
21+
: Either[BuildException, PreprocessedMarkdownCodeBlocks] = either {
22+
val mergedDirectives: ExtractedDirectives = cbs
23+
.map { cb =>
24+
value {
25+
ExtractedDirectives.from(
26+
contentChars = cb.body.toCharArray,
27+
path = reportingPath,
28+
logger = logger,
29+
supportedDirectives = UsingDirectiveKind.values(),
30+
cwd = scopePath / os.up,
31+
maybeRecoverOnError = maybeRecoverOnError
32+
)
33+
}
34+
}
35+
.fold(ExtractedDirectives.empty)(_ ++ _)
36+
PreprocessedMarkdownCodeBlocks(
37+
cbs,
38+
mergedDirectives
39+
)
40+
}
41+
PreprocessedMarkdown(
42+
value(preprocessCodeBlocks(scriptCodeBlocks)),
43+
value(preprocessCodeBlocks(rawCodeBlocks)),
44+
value(preprocessCodeBlocks(testCodeBlocks))
45+
)
46+
}
47+
}

modules/build/src/main/scala/scala/build/preprocessing/MarkdownPreprocessor.scala

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import scala.build.EitherCps.{either, value}
66
import scala.build.Logger
77
import scala.build.errors.BuildException
88
import scala.build.input.{Inputs, MarkdownFile, SingleElement, VirtualMarkdownFile}
9-
import scala.build.internal.markdown.MarkdownCodeWrapper
9+
import scala.build.internal.markdown.{MarkdownCodeBlock, MarkdownCodeWrapper}
1010
import scala.build.internal.{AmmUtil, CodeWrapper, CustomCodeWrapper, Name}
1111
import scala.build.options.{BuildOptions, BuildRequirements}
1212
import scala.build.preprocessing.ScalaPreprocessor.ProcessingOutput
@@ -67,24 +67,25 @@ case object MarkdownPreprocessor extends Preprocessor {
6767
allowRestrictedFeatures: Boolean
6868
): Either[BuildException, List[PreprocessedSource.InMemory]] = either {
6969
def preprocessSnippets(
70-
maybeCode: Option[String],
70+
maybeWrapper: Option[MarkdownCodeWrapper.WrappedMarkdownCode],
7171
generatedSourceNameSuffix: String
7272
): Either[BuildException, Option[PreprocessedSource.InMemory]] =
7373
either {
74-
maybeCode
75-
.map { code =>
76-
val processingOutput =
74+
maybeWrapper
75+
.map { wrappedMarkdown =>
76+
val processingOutput: ProcessingOutput =
7777
value {
7878
ScalaPreprocessor.process(
79-
content = code,
79+
content = wrappedMarkdown.code,
80+
extractedDirectives = wrappedMarkdown.directives,
8081
path = reportingPath,
8182
scopeRoot = scopePath / os.up,
8283
logger = logger,
8384
maybeRecoverOnError = maybeRecoverOnError,
8485
allowRestrictedFeatures = allowRestrictedFeatures
8586
)
8687
}.getOrElse(ProcessingOutput(BuildRequirements(), Nil, BuildOptions(), None))
87-
val processedCode = processingOutput.updatedContent.getOrElse(code)
88+
val processedCode = processingOutput.updatedContent.getOrElse(wrappedMarkdown.code)
8889
PreprocessedSource.InMemory(
8990
originalPath = reportingPath.map(subPath -> _),
9091
relPath = os.rel / (subPath / os.up) / s"${subPath.last}$generatedSourceNameSuffix",
@@ -99,8 +100,19 @@ case object MarkdownPreprocessor extends Preprocessor {
99100
}
100101
}
101102

103+
val codeBlocks: Seq[MarkdownCodeBlock] =
104+
value(MarkdownCodeBlock.findCodeBlocks(subPath, content, maybeRecoverOnError))
105+
val preprocessedMarkdown: PreprocessedMarkdown =
106+
value(MarkdownCodeBlockProcessor.process(
107+
codeBlocks,
108+
reportingPath,
109+
scopePath,
110+
logger,
111+
maybeRecoverOnError
112+
))
113+
102114
val (mainScalaCode, rawScalaCode, testScalaCode) =
103-
value(MarkdownCodeWrapper(subPath, content, maybeRecoverOnError))
115+
MarkdownCodeWrapper(subPath, preprocessedMarkdown)
104116

105117
val maybeMainFile = value(preprocessSnippets(mainScalaCode, ".scala"))
106118
val maybeRawFile = value(preprocessSnippets(rawScalaCode, ".raw.scala"))
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package scala.build.preprocessing
2+
3+
import scala.build.internal.markdown.MarkdownCodeBlock
4+
5+
case class PreprocessedMarkdownCodeBlocks(
6+
codeBlocks: Seq[MarkdownCodeBlock],
7+
extractedDirectives: ExtractedDirectives = ExtractedDirectives.empty
8+
)
9+
10+
object PreprocessedMarkdownCodeBlocks {
11+
def empty: PreprocessedMarkdownCodeBlocks =
12+
PreprocessedMarkdownCodeBlocks(Seq.empty, ExtractedDirectives.empty)
13+
}
14+
15+
case class PreprocessedMarkdown(
16+
scriptCodeBlocks: PreprocessedMarkdownCodeBlocks = PreprocessedMarkdownCodeBlocks.empty,
17+
rawCodeBlocks: PreprocessedMarkdownCodeBlocks = PreprocessedMarkdownCodeBlocks.empty,
18+
testCodeBlocks: PreprocessedMarkdownCodeBlocks = PreprocessedMarkdownCodeBlocks.empty
19+
)
20+
21+
object PreprocessedMarkdown {
22+
def empty: PreprocessedMarkdown = PreprocessedMarkdown()
23+
}

modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,43 @@ case object ScalaPreprocessor extends Preprocessor {
174174
logger: Logger,
175175
maybeRecoverOnError: BuildException => Option[BuildException],
176176
allowRestrictedFeatures: Boolean
177+
): Either[BuildException, Option[ProcessingOutput]] = either {
178+
val extractedDirectives = value(ExtractedDirectives.from(
179+
content.toCharArray,
180+
path,
181+
logger,
182+
UsingDirectiveKind.values(),
183+
scopeRoot,
184+
maybeRecoverOnError
185+
))
186+
value(process(
187+
content,
188+
extractedDirectives,
189+
path,
190+
scopeRoot,
191+
logger,
192+
maybeRecoverOnError,
193+
allowRestrictedFeatures
194+
))
195+
}
196+
197+
def process(
198+
content: String,
199+
extractedDirectives: ExtractedDirectives,
200+
path: Either[String, os.Path],
201+
scopeRoot: ScopePath,
202+
logger: Logger,
203+
maybeRecoverOnError: BuildException => Option[BuildException],
204+
allowRestrictedFeatures: Boolean
177205
): Either[BuildException, Option[ProcessingOutput]] = either {
178206
val (content0, isSheBang) = SheBang.ignoreSheBangLines(content)
179207
val afterStrictUsing: StrictDirectivesProcessingOutput =
180208
value(processStrictUsing(
181209
content0,
210+
extractedDirectives,
182211
path,
183212
scopeRoot,
184213
logger,
185-
maybeRecoverOnError,
186214
allowRestrictedFeatures
187215
))
188216

@@ -298,22 +326,14 @@ case object ScalaPreprocessor extends Preprocessor {
298326

299327
private def processStrictUsing(
300328
content: String,
329+
extractedDirectives: ExtractedDirectives,
301330
path: Either[String, os.Path],
302331
cwd: ScopePath,
303332
logger: Logger,
304-
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
305333
allowRestrictedFeatures: Boolean
306334
): Either[BuildException, StrictDirectivesProcessingOutput] = either {
307-
val contentChars = content.toCharArray
308-
val ExtractedDirectives(codeOffset, directives0) =
309-
value(ExtractedDirectives.from(
310-
contentChars,
311-
path,
312-
logger,
313-
UsingDirectiveKind.values(),
314-
cwd,
315-
maybeRecoverOnError
316-
))
335+
val contentChars = content.toCharArray
336+
val ExtractedDirectives(codeOffset, directives0) = extractedDirectives
317337

318338
val updatedOptions = value {
319339
DirectivesProcessor.process(

0 commit comments

Comments
 (0)