Skip to content

Commit 89fe628

Browse files
authored
Merge pull request #819 from romanowski/fix-docs-checkers
Fix sclicheck
2 parents dcc58d8 + 5ccc15b commit 89fe628

File tree

12 files changed

+201
-74
lines changed

12 files changed

+201
-74
lines changed

.github/scripts/check_docs.sh

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,28 @@ dest="$(pwd)/out/sclicheck/bin"
99
# so we try to run the scala-cli launcher with bash instead
1010
cat > "$dest/scala-cli" << EOF
1111
#!/usr/bin/env bash
12-
exec bash "$dest/scala-cli.sh"
12+
exec bash "$dest/scala-cli.sh" "\$@"
1313
EOF
1414
chmod +x "$dest/scala-cli"
1515

1616
echo "Adding $dest to PATH"
1717
export PATH="$dest:$PATH"
18-
ls "$dest"
1918

20-
if [ $# -eq 0 ]
21-
then
19+
if [ $# -eq 0 ]
20+
then
2221
toCheck=("website/docs/cookbooks" "website/docs/commands")
23-
else
22+
else
2423
toCheck=("$@")
2524
fi
2625

27-
# adding --resource-dirs is a hack to get file watching for free on .md files
28-
scala-cli sclicheck/sclicheck.scala docs -- "${toCheck[@]}"
26+
statusFile="$(pwd)/out/sclicheck/.status"
27+
scala-cli sclicheck/sclicheck.scala -- --status-file "$statusFile" "${toCheck[@]}" || (
28+
echo "Checking documentation failed. To run tests locally run `.github/scripts/check_docs.sh <failing_file>`"
29+
echo "You can find more about automatic documentaiton testing in sclicheck/Readme.md file."
30+
exit 1
31+
)
32+
33+
test -f "$statusFile" || (
34+
echo "Fatal error. Status file: $statusFile does not exists what signal problem with running tests."
35+
exit 1
36+
)

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ out/
55
.idea/
66

77
*.semanticdb
8+
.bsp
9+
.scala-build

sclicheck/sclicheck.scala

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import scala.util.matching.Regex
66
import scala.io.StdIn.readLine
77
import fansi.Color.{Red, Blue, Green}
88
import java.security.SecureRandom
9+
import scala.util.Random
910

1011
val SnippetBlock = """ *```[^ ]+ title=([\w\d\.\-\/_]+) *""".r
12+
val CompileBlock = """ *``` *(\w+) +(compile|fail) *(?:title=([\w\d\.\-\/_]+))? *""".r
1113
val CodeBlockEnds = """ *``` *""".r
1214
val BashCommand = """ *```bash *(fail)? *""".r
1315
val CheckBlock = """ *\<\!-- Expected(-regex)?: *""".r
@@ -18,6 +20,7 @@ case class Options(
1820
files: Seq[String] = Nil,
1921
dest: Option[os.Path] = None,
2022
stopAtFailure: Boolean = false,
23+
statusFile: Option[os.Path] = None,
2124
step: Boolean = false
2225
)
2326

@@ -34,9 +37,13 @@ enum Commands:
3437
cmd.mkString(prefix, " ", "")
3538
case Write(name, _, _) =>
3639
name
40+
case Compile(_, _, _, _) =>
41+
"compile snippet"
3742
}
3843

3944
case Write(fileName: String, lines: Seq[String], context: Context)
45+
46+
case Compile(fileName: String, lines: Seq[String], context: Context, shouldFail: Boolean)
4047
case Run(scriptLines: Seq[String], shouldFail: Boolean, context: Context)
4148
case Check(patterns: Seq[String], regex: Boolean, context: Context)
4249
case Clear(context: Context)
@@ -75,6 +82,10 @@ def parse(content: Seq[String], currentCommands: Seq[Commands], context: Context
7582
case SnippetBlock(name) :: tail =>
7683
parseMultiline(tail, Commands.Write(name, _, context))
7784

85+
case CompileBlock(name, status, fileName) :: tail =>
86+
val file = Option(fileName).getOrElse("snippet_" + Random.nextInt(1000) + "." + name)
87+
parseMultiline(tail, Commands.Compile(file, _, context, status == "fail"))
88+
7889
case BashCommand(failGroup) :: tail =>
7990
parseMultiline(tail, Commands.Run(_, failGroup != null, context))
8091

@@ -104,7 +115,7 @@ def checkPath(options: Options)(path: os.Path): Seq[TestCase] =
104115
toCheck.toList.flatMap(checkPath(options))
105116
catch
106117
case e @ FailedCheck(line, file, text) =>
107-
println(Red(e.getMessage))
118+
println(Console.RED + e.getMessage + Console.RESET)
108119
Seq(TestCase(path.relativeTo(os.pwd), Some(e.getMessage)))
109120
case e: Throwable =>
110121
val short = s"Unexpected exception ${e.getClass.getName}"
@@ -161,38 +172,54 @@ def checkFile(file: os.Path, options: Options): Unit =
161172
def runCommand(cmd: Commands, log: String => Unit) =
162173
given Context = cmd.context
163174

175+
def writeFile(file: os.Path, code: Seq[String], c: Context) =
176+
val (prefixLines, codeLines) =
177+
code match
178+
case shbang :: tail if shbang.startsWith("#!") =>
179+
List(shbang + "\n") -> tail
180+
case other =>
181+
Nil -> other
182+
183+
codeLines.foreach(log)
184+
185+
val prefix =
186+
if !shouldAlignContent(file) then prefixLines.mkString("")
187+
else prefixLines.mkString("", "", s"$fakeLineMarker\n" * c.line)
188+
189+
os.write.over(file, code.mkString(prefix, "\n", ""), createFolders = true)
190+
191+
def run(cmd: os.proc): Int =
192+
val res = cmd.call(cwd = out, mergeErrIntoOut = true, check = false)
193+
194+
log(res.out.text())
195+
196+
lastOutput = res.out.text()
197+
res.exitCode
198+
164199
cmd match
165200
case Commands.Run(cmds, shouldFail, _) =>
166201
val script = out / ".scala-build" / "run.sh"
167202
os.write.over(script, mkBashScript(cmds), createFolders = true)
168203
os.perms.set(script, "rwxr-xr-x")
169-
val res = os.proc(script).call(cwd = out, mergeErrIntoOut = true, check = false)
170-
171-
log(res.out.text())
172204

205+
val exitCode = run(os.proc(script))
173206
if shouldFail then
174-
check(res.exitCode != 0, s"Commands should fail.")
207+
check(exitCode != 0, s"Commands should fail.")
175208
else
176-
check(res.exitCode == 0, s"Commands failed.")
177-
178-
lastOutput = res.out.text()
209+
check(exitCode == 0, s"Commands failed.")
179210

180211
case Commands.Write(name, code, c) =>
181-
val (prefixLines, codeLines) =
182-
code match
183-
case shbang :: tail if shbang.startsWith("#!") =>
184-
List(shbang + "\n") -> tail
185-
case other =>
186-
Nil -> other
187-
188-
val file = out / os.RelPath(name)
189-
codeLines.foreach(log)
212+
writeFile(out / os.RelPath(name), code, c)
190213

191-
val prefix =
192-
if !shouldAlignContent(file) then prefixLines.mkString("")
193-
else prefixLines.mkString("", "", s"$fakeLineMarker\n" * c.line)
214+
case Commands.Compile(name, code, c, shouldFail) =>
215+
val dest = out / ".snippets" / name
216+
writeFile(dest, code, c)
194217

195-
os.write.over(file, code.mkString(prefix, "\n", ""), createFolders = true)
218+
val exitCode = run(os.proc("scala-cli", "compile", dest))
219+
if shouldFail then
220+
check(exitCode != 0, s"Compilation should fail.")
221+
else
222+
check(exitCode == 0, s"Compilation failed.")
196223

197224
case Commands.Check(patterns, regex, line) =>
198225
check(lastOutput != null, "No output stored from previous commands")
@@ -202,7 +229,7 @@ def checkFile(file: os.Path, options: Options): Unit =
202229
patterns.foreach { pattern =>
203230
val regex = pattern.r
204231
check(
205-
lines.exists(regex.matches),
232+
lines.exists(regex.findFirstIn(_).nonEmpty),
206233
s"Regex: $pattern, does not matches any line in:\n$lastOutput"
207234
)
208235
}
@@ -281,14 +308,20 @@ def checkFile(file: os.Path, options: Options): Unit =
281308

282309
os.list(out).filter(_.toString.endsWith(".scala")).foreach(p => os.copy.into(p, exampleDir))
283310

311+
def asPath(pathStr: String): os.Path =
312+
os.FilePath(pathStr) match
313+
case p: os.Path => p
314+
case s: os.SubPath => os.pwd / s
315+
case r: os.RelPath => os.pwd / r
316+
284317
@main def check(args: String*) =
285-
def processFiles(dest: Options) =
286-
val paths = dest.files.map { str =>
287-
val path = os.pwd / os.RelPath(str)
318+
def processFiles(options: Options) =
319+
val paths = options.files.map { str =>
320+
val path = asPath(str)
288321
assert(os.exists(path), s"Provided path $str does not exists in ${os.pwd}")
289322
path
290323
}
291-
val testCases = paths.flatMap(checkPath(dest))
324+
val testCases = paths.flatMap(checkPath(options))
292325
val (failed, ok) = testCases.partition(_.failure.nonEmpty)
293326
if testCases.size > 1 then
294327
if ok.nonEmpty then
@@ -303,20 +336,35 @@ def checkFile(file: os.Path, options: Options): Unit =
303336
println("")
304337
sys.exit(1)
305338

339+
options.statusFile.foreach { file =>
340+
os.write.over(file, s"Test completed:\n${testCases.map(_.path).mkString("\n")}")
341+
}
342+
343+
case class PathParameter(name: String):
344+
def unapply(args: Seq[String]): Option[(os.Path, Seq[String])] = args.match
345+
case `name` :: param :: tail =>
346+
if param.startsWith("--") then
347+
println(s"Please provide file name not an option: $param")
348+
sys.exit(1)
349+
Some((asPath(param), tail))
350+
case `name` :: Nil =>
351+
println(Red(s"Expected an argument after `--$name` parameter"))
352+
sys.exit(1)
353+
case _ => None
354+
355+
val Dest = PathParameter("--dest")
356+
val StatusFile = PathParameter("--status-file")
357+
306358
def parseArgs(args: Seq[String], options: Options): Options = args match
307359
case Nil => options
308360
case "--step" :: rest =>
309361
parseArgs(rest, options.copy(step = true))
310362
case "--stopAtFailure" :: rest =>
311363
parseArgs(rest, options.copy(stopAtFailure = true))
312-
case "--dest" :: dest :: rest =>
313-
if dest.startsWith("--") then
314-
println(s"Please provide file name not an option: $dest")
315-
316-
parseArgs(rest, options.copy(dest = Some(os.pwd / dest)))
317-
case "--dest" :: Nil =>
318-
println(Red("Exptected a destanation after `--dest` parameter"))
319-
sys.exit(1)
364+
case Dest(dest, rest) =>
365+
parseArgs(rest, options.copy(dest = Some(dest)))
366+
case StatusFile(file, rest) =>
367+
parseArgs(rest, options.copy(statusFile = Some(file)))
320368
case path :: rest => parseArgs(rest, options.copy(files = options.files :+ path))
321369

322370
processFiles(parseArgs(args, Options()))

sclicheck/sclicheck.test.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//> using lib "org.scalameta::munit::0.7.29"
2+
3+
class SclicheckTest extends munit.FunSuite:
4+
test("Run regex") {
5+
assert(
6+
Some(Seq("scala", "compile", null)) == clue(CompileBlock.unapplySeq("```scala compile"))
7+
)
8+
assert(
9+
Some(Seq("scala", "fail", null)) == clue(CompileBlock.unapplySeq("```scala fail"))
10+
)
11+
assert(
12+
Some(Seq("scala", "fail", "a.sc")) == clue(
13+
CompileBlock.unapplySeq("```scala fail title=a.sc")
14+
)
15+
)
16+
}

website/docs/commands/basics.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,12 @@ Running another Scala CLI version might be slower because it uses JVM-based Scal
191191
To run another Scala CLI version, specify it with `--cli-version` before any other argument:
192192

193193
```bash
194-
scala-cli --cli-version 0.0.9+131-gf0ab5c40-SNAPSHOT about
195-
# Scala CLI version 0.0.9+131-gf0ab5c40-SNAPSHOT (v0.0.9-131-f0ab5c)
194+
scala-cli --cli-version 0.1.2+209-geb58be9d-SNAPSHOT about
195+
# Scala CLI version 0.1.2+209-geb58be9d-SNAPSHOT (v0.0.9-131-f0ab5c)
196196
```
197197

198198
<!-- Expected:
199-
Scala CLI version 0.0.9+131-gf0ab5c40-SNAPSHOT (v0.0.9-131-f0ab5c)
199+
Scala CLI version 0.1.2+209-geb58be9d-SNAPSHOT
200200
-->
201201

202202
## Process substitution

website/docs/commands/package.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,8 @@ scala-cli package --docker HelloDocker.scala --docker-image-repository hello-doc
119119
```
120120

121121
<!-- Expected:
122-
Started building docker image with your application, it would take some time
123-
Built docker image, run it with
124-
docker run hello-docker:latest
122+
Started building docker image with your application
123+
docker run hello-docker:latest
125124
-->
126125

127126
```bash
@@ -140,9 +139,8 @@ This command shows how to create a Docker image (`--docker`) for a Scala JS (`--
140139
scala-cli package --js --docker HelloDocker.scala --docker-image-repository hello-docker
141140
```
142141
<!-- Expected:
143-
Started building docker image with your application, it would take some time
144-
Built docker image, run it with
145-
docker run hello-docker:latest
142+
Started building docker image with your application
143+
docker run hello-docker:latest
146144
-->
147145

148146
Packaging Scala Native applications to a Docker image is only supported on Linux.

website/docs/cookbooks/instant-startup-scala-scripts.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: Scala Scripts with instant startup
33
sidebar_position: 8
44
---
55

6+
import {ChainedSnippets, GiflikeVideo} from "../src/components/MarkdownComponents.js";
7+
68
`scala-cli` allows to easly compile and run Scala Scripts.
79
It also allows for straightforward compilation with Scala Native.
810
Scala Native is an ahead-of-time compiler to native binary allowing
@@ -14,7 +16,7 @@ perfectly suit the needs of a fast scripting tool.
1416
As an example, let’s build a script printing files from
1517
a directory with sizes bigger than a passed value.
1618

17-
```scala title=size-higher-than.scala
19+
```scala compile title=size-higher-than.scala
1820
//> using scala "3.1.1"
1921
//> using lib "com.lihaoyi::os-lib::0.8.1"
2022

@@ -30,18 +32,25 @@ def sizeHigherThan(dir: String, minSizeMB: Int) =
3032
Running this for a `dir` directory and 20 MB as a lower limit with
3133
`scala-cli size-higher-than.scala – dir 20` can give us for example:
3234

33-
```bash
35+
36+
<ChainedSnippets>
37+
38+
```bash ignore
3439
scala-cli size-higher-than.scala -- dir 20
40+
```
41+
42+
```
3543
Compiling project (Scala 3.1.1, JVM)
3644
Compiled project (Scala 3.1.1, JVM)
3745
/Users/user/Documents/workspace/dir/large-file.txt
3846
```
47+
</ChainedSnippets>
3948

4049
A keen eye will notice that we have not yet compiled to Scala Native. We are still running on the JVM!
4150
We can fix that by either running with a `—-native` option, or,
4251
in this case, by including an additional using directive:
4352

44-
```scala title=size-higher-than.scala
53+
```scala compile title=size-higher-than.scala
4554
//> using scala "3.1.1"
4655
//> using lib "com.lihaoyi::os-lib::0.8.1"
4756
//> using platform "scala-native"
@@ -67,11 +76,11 @@ We can make the runtime itself even faster, using various Scala Native optimizat
6776

6877
We pass these using a `-–native-mode` scala-cli option or, like previously, by adding a using directive:
6978

70-
```scala title=size-higher-than.scala
79+
```scala compile title=size-higher-than.scala
7180
//> using scala "3.1.1"
7281
//> using lib "com.lihaoyi::os-lib::0.8.1"
7382
//> using platform "scala-native"
74-
//> using native-mode release-full
83+
//> using nativeMode "release-full"
7584

7685
@main
7786
def sizeHigherThan(dir: String, minSizeMB: Int) =

0 commit comments

Comments
 (0)