11package scala .build .preprocessing
22
3+ import coursier .cache .ArchiveCache
4+ import coursier .util .Task
5+
36import java .nio .charset .StandardCharsets
47
58import scala .build .EitherCps .{either , value }
69import scala .build .Logger
710import scala .build .errors .BuildException
811import scala .build .input .{MarkdownFile , ScalaCliInvokeData , SingleElement , VirtualMarkdownFile }
12+ import scala .build .internal .JavaParserProxyMaker
913import scala .build .internal .markdown .{MarkdownCodeBlock , MarkdownCodeWrapper }
1014import scala .build .options .SuppressWarningOptions
1115import scala .build .preprocessing .ScalaPreprocessor .ProcessingOutput
16+ import scala .build .preprocessing .directives .PreprocessedDirectives
1217
13- case object MarkdownPreprocessor extends Preprocessor {
18+ /** Markdown source preprocessor.
19+ *
20+ * @param archiveCache
21+ * when using a java-class-name external binary to infer a class name (see [[JavaParserProxy ]]),
22+ * a cache to download that binary with
23+ * @param javaClassNameVersionOpt
24+ * when using a java-class-name external binary to infer a class name (see [[JavaParserProxy ]]),
25+ * this forces the java-class-name version to download
26+ */
27+ final case class MarkdownPreprocessor (
28+ archiveCache : ArchiveCache [Task ],
29+ javaClassNameVersionOpt : Option [String ],
30+ javaCommand : () => String
31+ ) extends Preprocessor {
1432 def preprocess (
1533 input : SingleElement ,
1634 logger : Logger ,
@@ -23,15 +41,18 @@ case object MarkdownPreprocessor extends Preprocessor {
2341 val res = either {
2442 val content = value(PreprocessingUtil .maybeRead(markdown.path))
2543 val preprocessed = value {
26- MarkdownPreprocessor .preprocess(
27- Right (markdown.path),
28- content,
29- markdown.subPath,
30- ScopePath .fromPath(markdown.path),
31- logger,
32- maybeRecoverOnError,
33- allowRestrictedFeatures,
34- suppressWarningOptions
44+ preprocessContent(
45+ archiveCache = archiveCache,
46+ javaClassNameVersionOpt = javaClassNameVersionOpt,
47+ javaCommand = javaCommand,
48+ reportingPath = Right (markdown.path),
49+ content = content,
50+ subPath = markdown.subPath,
51+ scopePath = ScopePath .fromPath(markdown.path),
52+ logger = logger,
53+ maybeRecoverOnError = maybeRecoverOnError,
54+ allowRestrictedFeatures = allowRestrictedFeatures,
55+ suppressWarningOptions = suppressWarningOptions
3556 )
3657 }
3758 preprocessed
@@ -41,15 +62,18 @@ case object MarkdownPreprocessor extends Preprocessor {
4162 val content = new String (markdown.content, StandardCharsets .UTF_8 )
4263 val res = either {
4364 val preprocessed = value {
44- MarkdownPreprocessor .preprocess(
45- Left (markdown.source),
46- content,
47- markdown.wrapperPath,
48- markdown.scopePath,
49- logger,
50- maybeRecoverOnError,
51- allowRestrictedFeatures,
52- suppressWarningOptions
65+ preprocessContent(
66+ archiveCache = archiveCache,
67+ javaClassNameVersionOpt = javaClassNameVersionOpt,
68+ javaCommand = javaCommand,
69+ reportingPath = Left (markdown.source),
70+ content = content,
71+ subPath = markdown.wrapperPath,
72+ scopePath = markdown.scopePath,
73+ logger = logger,
74+ maybeRecoverOnError = maybeRecoverOnError,
75+ allowRestrictedFeatures = allowRestrictedFeatures,
76+ suppressWarningOptions = suppressWarningOptions
5377 )
5478 }
5579 preprocessed
@@ -59,7 +83,10 @@ case object MarkdownPreprocessor extends Preprocessor {
5983 None
6084 }
6185
62- private def preprocess (
86+ private def preprocessContent (
87+ archiveCache : ArchiveCache [Task ],
88+ javaClassNameVersionOpt : Option [String ],
89+ javaCommand : () => String ,
6390 reportingPath : Either [String , os.Path ],
6491 content : String ,
6592 subPath : os.SubPath ,
@@ -106,6 +133,38 @@ case object MarkdownPreprocessor extends Preprocessor {
106133 }
107134 }
108135
136+ def emitJavaSnippets (
137+ blocks : PreprocessedMarkdownCodeBlocks ,
138+ isTest : Boolean
139+ ): Either [BuildException , List [PreprocessedSource .InMemory ]] =
140+ either {
141+ val javaParser =
142+ (new JavaParserProxyMaker )
143+ .get(
144+ archiveCache,
145+ javaClassNameVersionOpt,
146+ logger,
147+ () => javaCommand()
148+ )
149+ blocks.codeBlocks.zipWithIndex.map { (block, index) =>
150+ value {
151+ emitJavaSnippet(
152+ block = block,
153+ index = index,
154+ isTest = isTest,
155+ subPath = subPath,
156+ scopePath = scopePath,
157+ reportingPath = reportingPath,
158+ javaParser = javaParser,
159+ logger = logger,
160+ allowRestrictedFeatures = allowRestrictedFeatures,
161+ suppressWarningOptions = suppressWarningOptions,
162+ maybeRecoverOnError = maybeRecoverOnError
163+ )
164+ }
165+ }.toList
166+ }
167+
109168 val codeBlocks : Seq [MarkdownCodeBlock ] =
110169 value(MarkdownCodeBlock .findCodeBlocks(subPath, content, maybeRecoverOnError))
111170 val preprocessedMarkdown : PreprocessedMarkdown =
@@ -123,8 +182,60 @@ case object MarkdownPreprocessor extends Preprocessor {
123182 val maybeMainFile = value(preprocessSnippets(mainScalaCode, " .scala" ))
124183 val maybeRawFile = value(preprocessSnippets(rawScalaCode, " .raw.scala" ))
125184 val maybeTestFile = value(preprocessSnippets(testScalaCode, " .test.scala" ))
185+ val javaFiles = value(emitJavaSnippets(preprocessedMarkdown.javaCodeBlocks, isTest = false ))
186+ val javaTestFiles =
187+ value(emitJavaSnippets(preprocessedMarkdown.javaTestCodeBlocks, isTest = true ))
126188
127- maybeMainFile.toList ++ maybeTestFile ++ maybeRawFile
189+ maybeMainFile.toList ++ maybeTestFile ++ maybeRawFile ++ javaFiles ++ javaTestFiles
128190 }
129191
192+ private def emitJavaSnippet (
193+ block : MarkdownCodeBlock ,
194+ index : Int ,
195+ isTest : Boolean ,
196+ subPath : os.SubPath ,
197+ scopePath : ScopePath ,
198+ reportingPath : Either [String , os.Path ],
199+ javaParser : scala.build.internal.JavaParserProxy ,
200+ logger : Logger ,
201+ allowRestrictedFeatures : Boolean ,
202+ suppressWarningOptions : SuppressWarningOptions ,
203+ maybeRecoverOnError : BuildException => Option [BuildException ]
204+ )(using ScalaCliInvokeData ): Either [BuildException , PreprocessedSource .InMemory ] = either {
205+ val classNameOpt = value {
206+ javaParser.className(block.body.getBytes(StandardCharsets .UTF_8 ))
207+ }
208+ val mdBaseName = subPath.last.stripSuffix(" .md" )
209+ val baseName = classNameOpt.getOrElse(s " ${mdBaseName}_md_snippet $index" )
210+ val javaFileName =
211+ if isTest then s " $baseName.test.java "
212+ else s " $baseName.java "
213+ val generatedRelPath =
214+ if isTest then os.rel / (subPath / os.up) / s " $baseName.java "
215+ else os.rel / (subPath / os.up) / javaFileName
216+ val snippetScopePath = scopePath.copy(subPath = (subPath / os.up) / javaFileName)
217+ val preprocessedDirectives : PreprocessedDirectives = value {
218+ DirectivesPreprocessor (
219+ reportingPath,
220+ snippetScopePath,
221+ logger,
222+ allowRestrictedFeatures,
223+ suppressWarningOptions,
224+ maybeRecoverOnError
225+ ).preprocess(block.body)
226+ }
227+ PreprocessedSource .InMemory (
228+ originalPath = reportingPath.map(subPath -> _),
229+ relPath = generatedRelPath,
230+ content = block.body.getBytes(StandardCharsets .UTF_8 ),
231+ wrapperParamsOpt = None ,
232+ options = Some (preprocessedDirectives.globalUsings),
233+ optionsWithTargetRequirements = preprocessedDirectives.usingsWithReqs,
234+ requirements = Some (preprocessedDirectives.globalReqs),
235+ scopedRequirements = preprocessedDirectives.scopedReqs,
236+ mainClassOpt = None ,
237+ scopePath = snippetScopePath,
238+ directivesPositions = preprocessedDirectives.directivesPositions
239+ )
240+ }
130241}
0 commit comments