diff --git a/.github/workflows/hugo.yml b/.github/workflows/hugo.yml index 9f8e1c499..7c9673ab5 100644 --- a/.github/workflows/hugo.yml +++ b/.github/workflows/hugo.yml @@ -42,19 +42,34 @@ jobs: HUGO_VERSION: 0.112.0 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - name: Install Hugo CLI - run: | - wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ - && sudo dpkg -i ${{ runner.temp }}/hugo.deb + - name: Set Up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'adopt-hotspot' + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v3 + with: + hugo-version: '0.112.0' + extended: true + - name: Install Dart Sass run: sudo snap install dart-sass + - name: Checkout uses: actions/checkout@v4 with: submodules: recursive + - name: Setup Pages id: pages uses: actions/configure-pages@v4 + + - name: Generate ScalaDoc + run: | + sbt doc/test doc/unidoc + - name: Build with Hugo env: # For maximum backward compatibility with Hugo modules @@ -64,9 +79,8 @@ jobs: cd doc/src/main/hugo ; \ hugo --minify --baseURL "https://riddl.tech" \ --printMemoryUsage --noBuildLock --cleanDestinationDir \ - --enableGitInfo --noBuildLock --printPathWarnings --printMemoryUsage ; \ - cd ../../../.. ; \ - sbt "project doc" "test" + --enableGitInfo --noBuildLock --printPathWarnings --printMemoryUsage + - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index d31d2be1e..d56aca3e3 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -34,27 +34,33 @@ jobs: - name: Coursier Caching uses: coursier/cache-action@v6 - - name: Build, Run Test, Coverage + - name: Build, Run Test env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: Scala ${{ matrix.scala }} RIDDLC_PATH: riddlc/target/universal/stage/bin/riddlc run: | - sbt -v clean coverage Test/compile test coverageAggregate coveralls + sbt -v clean Test/compile test - - name: Archive code coverage results + - name: Coverage For JVM projects only + run: | + sbt -v clean coverage \ + utils/test language/test passes/test language/test diagrams/test \ + command/test diagrams/test command/test prettify/test hugo/test \ + commands/test riddlc/test coverageAggregate coveralls + + - name: Coverage Results Collection uses: actions/upload-artifact@v4 with: name: code-coverage-report path: | - - **/target/scala-3.4.1/scoverage-report/scoverage.xml - - target/scala-3.4.1/scoverage-report/scoverage.xml + - **/target/scala-3.4.2/scoverage-report/scoverage.xml + - target/scala-3.4.2/scoverage-report/scoverage.xml - sbt-riddl/target/scala-2.12/scoverage-report/scoverage.xml - name: Test sbt-riddl plugin + env: + RIDDLC_PATH: riddlc/target/universal/stage/bin/riddlc run: | - export RIDDLC_PATH=riddlc/target/universal/stage/bin/riddlc - sbt -v clean compile publishLocal "project riddlc" "stage" "project sbt-riddl" scripted + sbt -v clean compile publishLocal riddlc/stage sbt-riddl/scripted - name: Cleanup Before Caching shell: bash diff --git a/.gitignore b/.gitignore index cfa00ceed..5023ac2cc 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ target *.lock /hugo-input/ /scoverage-report/ +/package-lock.json +/node_modules diff --git a/README.md b/README.md index 2ba2a4c1c..3f46d465e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Documentation Build Status](https://github.com/ossuminc/riddl/actions/workflows/hugo.yml/badge.svg)](https://github.com/ossuminc/riddl/actions/workflows/hugo.yml/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/ossuminc/riddl/badge.svg?branch=main)](https://coveralls.io/github/ossuminc/riddl?branch=main) [![Maven Central](https://img.shields.io/maven-central/v/com.ossuminc/riddlc_3.svg)](https://maven-badges.herokuapp.com/maven-central/com.ossuminc/riddlc_3) +[![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.16.0.svg)](https://www.scala-js.org) [![CLA assistant](https://cla-assistant.io/readme/badge/ossuminc/riddl)](https://cla-assistant.io/ossuminc/riddl) [![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) diff --git a/build.sbt b/build.sbt index 63323ec25..171c83a8f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,8 @@ import org.scoverage.coveralls.Imports.CoverallsKeys.* -import com.ossuminc.sbt.OssumIncPlugin +import com.ossuminc.sbt.{OssumIncPlugin, Plugin,DocSite,CrossModule} +import sbt.Keys.{description, libraryDependencies} +import sbtbuildinfo.BuildInfoPlugin.autoImport.buildInfoPackage +import sbtcrossproject.{CrossClasspathDependency, CrossProject} Global / onChangedBuildSource := ReloadOnSourceChanges (Global / excludeLintKeys) ++= Set(mainClass) @@ -8,19 +11,23 @@ enablePlugins(OssumIncPlugin) lazy val startYear: Int = 2019 -def comptest(p: Project): ClasspathDependency = p % "compile->compile;test->test" +def cpDep(cp: CrossProject): CrossClasspathDependency = cp % "compile->compile;test->test" +def pDep(p: Project): ClasspathDependency = p % "compile->compile;test->test" lazy val riddl: Project = Root("riddl", startYr = startYear) .configure(With.noPublishing, With.git, With.dynver) .aggregate( utils, + utilsJS, language, + languageJS, passes, + passesJS, + diagrams, + diagramsJS, command, - analyses, prettify, hugo, - testkit, commands, riddlc, docsite, @@ -28,92 +35,105 @@ lazy val riddl: Project = Root("riddl", startYr = startYear) ) lazy val Utils = config("utils") -lazy val utils: Project = Module("utils", "riddl-utils") - .configure(With.typical, With.build_info, With.coverage(70) /*, With.native()*/ ) - .configure(With.publishing) +lazy val utils_cp: CrossProject = CrossModule("utils", "riddl-utils")(JVM, JS) + .configure(With.typical) + .configure(With.build_info) .settings( - coverageExcludedFiles := """;$anon;.*RiddlBuildInfo.scala""", - scalaVersion := "3.4.1", - scalacOptions += "--no-warnings", + scalaVersion := "3.4.2", + scalacOptions += "-explain-cyclic", buildInfoPackage := "com.ossuminc.riddl.utils", buildInfoObject := "RiddlBuildInfo", - description := "Various utilities used throughout riddl libraries", + description := "Various utilities used throughout riddl libraries" + ) + .jvmConfigure(With.coverage(70)) + .jvmConfigure(With.publishing) + .jvmSettings( + coverageExcludedFiles := """;$anon;.*RiddlBuildInfo.scala""", libraryDependencies ++= Seq(Dep.compress, Dep.lang3) ++ Dep.testing ) + .jsConfigure(With.js("RIDDL: utils", withCommonJSModule = true)) + .jsSettings( + libraryDependencies ++= Seq( + "org.scala-js" %%% "scalajs-dom" % "2.8.0", + "io.github.cquiroz" %%% "scala-java-time" % "2.6.0", + "org.scalactic" %%% "scalactic" % V.scalatest % "test", + "org.scalatest" %%% "scalatest" % V.scalatest % "test", + "org.scalacheck" %%% "scalacheck" % V.scalacheck % "test" + ) + ) + +lazy val utils = utils_cp.jvm +lazy val utilsJS = utils_cp.js val Language = config("language") -lazy val language: Project = Module("language", "riddl-language") - .configure(With.typical, With.coverage(65)) - .configure(With.publishing) +lazy val language_cp: CrossProject = CrossModule("language", "riddl-language")(JVM, JS) + .dependsOn(cpDep(utils_cp)) + .configure(With.typical) .settings( - scalaVersion := "3.4.1", - scalacOptions ++= Seq("-explain", "--explain-types", "--explain-cyclic", "--no-warnings"), - coverageExcludedPackages := ";$anon", description := "Abstract Syntax Tree and basic RIDDL language parser", - libraryDependencies ++= Dep.testing ++ Seq(Dep.fastparse, Dep.commons_io, Dep.jacabi_w3c) + scalacOptions ++= Seq("-explain", "--explain-types", "--explain-cyclic", "--no-warnings") + ) + .jvmConfigure(With.coverage(65)) + .jvmConfigure(With.publishing) + .jvmSettings( + coverageExcludedPackages := ";$anon", + libraryDependencies ++= Dep.testing ++ Seq(Dep.fastparse), + libraryDependencies += Dep.commons_io % Test ) - .dependsOn(utils) + .jsConfigure(With.js("RIDDL: language", withCommonJSModule = true)) + .jsSettings( + libraryDependencies += "com.lihaoyi" %%% "fastparse" % V.fastparse + ) +lazy val language = language_cp.jvm.dependsOn(utils) +lazy val languageJS = language_cp.js.dependsOn(utilsJS) val Passes = config("passes") -lazy val passes = Module("passes", "riddl-passes") - .configure(With.typical, With.coverage(30)) - .configure(With.publishing) +lazy val passes_cp = CrossModule("passes", "riddl-passes")(JVM, JS) + .dependsOn(cpDep(utils_cp), cpDep(language_cp)) + .configure(With.typical, With.publishing) .settings( - scalaVersion := "3.4.1", scalacOptions ++= Seq("-explain", "--explain-types", "--explain-cyclic"), + description := "AST Pass infrastructure and essential passes" + ) + .jvmConfigure(With.coverage(30)) + .jvmSettings( coverageExcludedPackages := ";$anon", - description := "AST Pass infrastructure and essential passes", - libraryDependencies ++= Dep.testing ) - .dependsOn(utils, comptest(language)) + .jsConfigure(With.js("RIDDL: passes", withCommonJSModule = true)) +val passes = passes_cp.jvm +val passesJS = passes_cp.js + +val Diagrams = config("diagrams") +lazy val diagrams_cp: CrossProject = CrossModule("diagrams", "riddl-diagrams")(JVM, JS) + .dependsOn(cpDep(utils_cp), cpDep(language_cp), cpDep(passes_cp)) + .configure(With.typical,With.publishing) + .settings( + description := "Implementation of various AST diagrams passes other libraries may use" + ) + .jvmConfigure(With.coverage(50)) + .jvmSettings( + coverageExcludedFiles := """;$anon""", + ) + .jsConfigure(With.js("RIDDL: diagrams", withCommonJSModule = true)) +val diagrams = diagrams_cp.jvm +val diagramsJS = diagrams_cp.js val Command = config("command") lazy val command = Module("command", "riddl-command") .configure(With.typical, With.coverage(30)) .configure(With.publishing) .settings( - scalaVersion := "3.4.1", - scalacOptions ++= Seq("-explain", "--explain-types", "--explain-cyclic"), coverageExcludedPackages := ";$anon", description := "Command infrastructure needed to define a command", - libraryDependencies ++= Seq(Dep.scopt, Dep.pureconfig) ++ Dep.testing + libraryDependencies ++= Seq( + Dep.scopt, Dep.pureconfig, + "org.scala-js" %% "scalajs-stubs" % "1.1.0" % "provided" + ) ++ Dep.testing ) - .dependsOn(comptest(utils), comptest(language), passes) + .dependsOn(pDep(utils), pDep(language), passes) def testDep(project: Project): ClasspathDependency = project % "compile->compile;compile->test;test->test" -val TestKit = config("testkit") -lazy val testkit: Project = Module("testkit", "riddl-testkit") - .configure(With.typical) - .configure(With.publishing) - .settings( - coverageExcludedFiles := """;$anon""", - scalaVersion := "3.4.1", - scalacOptions += "--no-warnings", - description := "A Testkit for testing RIDDL code, and a suite of those tests", - libraryDependencies ++= Dep.testKitDeps - ) - .dependsOn( - testDep(language), - testDep(passes), - testDep(command) - ) - -val Analyses = config("analyses") -lazy val analyses: Project = Module("analyses", "riddl-analyses") - .configure(With.typical) - .configure(With.coverage(50)) - .configure(With.publishing) - .settings( - coverageExcludedFiles := """;$anon""", - scalaVersion := "3.4.1", - description := "Implementation of various AST analyses passes other libraries may use", - libraryDependencies ++= Seq(Dep.pureconfig) ++ Dep.testing - ) - .dependsOn(utils, language, comptest(passes)) - -def testKitDep: ClasspathDependency = testkit % "test->compile;test->test" - val Prettify = config("prettify") lazy val prettify = Module("prettify", "riddl-prettify") .configure(With.typical) @@ -121,11 +141,13 @@ lazy val prettify = Module("prettify", "riddl-prettify") .configure(With.publishing) .settings( coverageExcludedFiles := """;$anon""", - scalaVersion := "3.4.1", + scalaVersion := "3.4.2", description := "Implementation for the RIDDL prettify command, a code reformatter", - libraryDependencies ++= Dep.testing + libraryDependencies ++= Dep.testing ++ Seq( + "org.scala-js" %% "scalajs-stubs" % "1.1.0" % "provided" + ) ) - .dependsOn(utils, language, comptest(passes), command, testKitDep) + .dependsOn(utils, language, pDep(passes),command) val Hugo = config("hugo") lazy val hugo = Module("hugo", "riddl-hugo") @@ -134,12 +156,14 @@ lazy val hugo = Module("hugo", "riddl-hugo") .configure(With.publishing) .settings( coverageExcludedFiles := """;$anon""", - scalaVersion := "3.4.1", + scalaVersion := "3.4.2", scalacOptions += "-explain-cyclic", description := "Implementation for the RIDDL prettify command, a code reformatter", - libraryDependencies ++= Dep.testing + libraryDependencies ++= Dep.testing ++ Seq( + "org.scala-js" %% "scalajs-stubs" % "1.1.0" % "provided" + ) ) - .dependsOn(utils, comptest(language), comptest(passes), comptest(command), analyses, testKitDep) + .dependsOn(utils, pDep(language), pDep(passes), diagrams, pDep(command), prettify) val Commands = config("commands") lazy val commands: Project = Module("commands", "riddl-commands") @@ -148,20 +172,19 @@ lazy val commands: Project = Module("commands", "riddl-commands") .configure(With.publishing) .settings( coverageExcludedFiles := """;$anon""", - scalaVersion := "3.4.1", scalacOptions ++= Seq("-explain", "--explain-types", "--explain-cyclic"), description := "RIDDL Command Infrastructure and basic command definitions", - libraryDependencies ++= Dep.testing + libraryDependencies ++= Seq(Dep.scopt,Dep.pureconfig) ++ Dep.testing ++ Seq( + "org.scala-js" %% "scalajs-stubs" % "1.1.0" % "provided" + ) ) .dependsOn( - comptest(utils), - comptest(language), - comptest(passes), + pDep(utils), + pDep(language), + pDep(passes), command, - analyses, prettify, - hugo, - testKitDep + hugo ) val Riddlc = config("riddlc") @@ -172,15 +195,13 @@ lazy val riddlc: Project = Program("riddlc", "riddlc") .dependsOn( utils, language, - passes, - analyses, + testDep(passes), prettify, - commands, - testkit % "test->compile" + testDep(commands) ) .settings( coverageExcludedFiles := """;$anon""", - scalaVersion := "3.4.1", + scalaVersion := "3.4.2", description := "The `riddlc` compiler and tests, the only executable in RIDDL", coverallsTokenFile := Some("/home/reid/.coveralls.yml"), maintainer := "reid@ossuminc.com", @@ -199,9 +220,8 @@ lazy val docProjects = List( (utils, Utils), (language, Language), (passes, Passes), + (diagrams, Diagrams), (command, Command), - (testkit, TestKit), - (analyses, Analyses), (prettify, Prettify), (hugo, Hugo), (commands, Commands), @@ -210,16 +230,29 @@ lazy val docProjects = List( lazy val docOutput: File = file("doc") / "src" / "main" / "hugo" / "static" / "apidoc" -lazy val docsite = DocSite("doc", docOutput, docProjects) +//def akkaMappings: Map[(String, String), URL] = Map( +// ("com.typesafe.akka", "akka-actor") -> url(s"http://doc.akka.io/api/akka/"), +// ("com.typesafe", "config") -> url("http://typesafehub.github.io/config/latest/api/") +//) + +lazy val docsite = DocSite( + dirName = "doc", + apiOutput = file( "src") / "main" / "hugo" / "static" / "apidoc", + baseURL = Some("https://riddl.tech/apidoc"), + inclusions = Seq(utils,language,passes,diagrams,command,prettify,hugo,commands), + logoPath = Some("doc/src/main/hugo/static/images/RIDDL-Logo-128x128.png") +) .settings( name := "riddl-doc", - scalaVersion := "3.4.1", + scalaVersion := "3.4.2", description := "Generation of the documentation web site", libraryDependencies ++= Dep.testing - ) + ).dependsOn(utils,language,passes,diagrams,command,prettify,hugo,commands) + lazy val plugin = Plugin("sbt-riddl") .configure(With.build_info) + .configure(With.scala2) .configure(With.publishing) .settings( description := "An sbt plugin to embellish a project with riddlc usage", diff --git a/command/src/main/scala/com/ossuminc/riddl/command/Command.scala b/command/src/main/scala/com/ossuminc/riddl/command/Command.scala new file mode 100644 index 000000000..466966616 --- /dev/null +++ b/command/src/main/scala/com/ossuminc/riddl/command/Command.scala @@ -0,0 +1,173 @@ +/* + * Copyright 2019 Ossum, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ossuminc.riddl.command + +import com.ossuminc.riddl.language.{At, CommonOptions} +import com.ossuminc.riddl.language.Messages +import com.ossuminc.riddl.language.Messages.Messages +import com.ossuminc.riddl.language.Messages.SevereError +import com.ossuminc.riddl.language.Messages.errors +import com.ossuminc.riddl.language.Messages.highestSeverity +import com.ossuminc.riddl.language.Messages.severes +import com.ossuminc.riddl.utils.* +import com.ossuminc.riddl.passes.PassesResult +import pureconfig.ConfigReader +import pureconfig.ConfigSource +import scopt.OParser +import scopt.OParserBuilder + +import java.io.File +import java.nio.file.Path +import scala.annotation.unused +import scala.jdk.CollectionConverters.CollectionHasAsScala +import scala.reflect.ClassTag +import scala.reflect.classTag +import scala.util.control.NonFatal + + /** The service interface for Riddlc command plugins */ +trait Command[OPT <: CommandOptions: ClassTag](val pluginName: String): + + private val optionsClass : Class[?] = classTag[OPT].runtimeClass + + /** Provide an scopt OParser for the commands options type, OPT + * @return + * A pair: the OParser and the default values for OPT + */ + def getOptionsParser: (OParser[Unit, OPT], OPT) + + def parseOptions( + args: Array[String] + ): Option[OPT] = { + val (parser, default) = getOptionsParser + val (result, effects) = OParser.runParser(parser, args, default) + OParser.runEffects(effects) + result + } + + /** Provide a typesafe/Config reader for the commands options. This reader should read an object having the same name + * as the command. The fields of that object must correspond to the fields of the OPT type. + * @return + * A pureconfig.ConfigReader[OPT] that knows how to read OPT + */ + def getConfigReader: ConfigReader[OPT] + + def loadOptionsFrom( + configFile: Path, + commonOptions: CommonOptions = CommonOptions() + ): Either[Messages, OPT] = { + if commonOptions.verbose then { + println(s"Reading command options from: $configFile") + } + ConfigSource.file(configFile).load[OPT](getConfigReader) match { + case Right(value) => + if commonOptions.verbose then { + println(s"Read command options from $configFile") + } + if commonOptions.debug then { + println(StringHelpers.toPrettyString(value, 1)) + } + Right(value) + case Left(fails) => + Left( + errors( + s"Errors while reading $configFile:\n" + fails.prettyPrint(1) + ) + ) + } + } + + /** Execute the command given the options. Error should be returned as Left(messages) and not directly logged. The log + * is for verbose or debug output + * + * @param options + * The command specific options + * @param commonOptions + * The options common to all commands + * @param log + * A logger for logging errors, warnings, and info + * @return + * Either a set of Messages on error or a Unit on success + */ + def run( + @unused options: OPT, + @unused commonOptions: CommonOptions, + @unused log: Logger, + @unused outputDirOverride: Option[Path] + ): Either[Messages, PassesResult] = + Left( + severes( + s"""In command '$pluginName': + |the CommandPlugin.run(OPT,CommonOptions,Logger) method was not overridden""".stripMargin + ) + ) + end run + + def run( + args: Array[String], + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] = None + ): Either[Messages, PassesResult] = + val maybeOptions: Option[OPT] = parseOptions(args) + maybeOptions match + case Some(opts: OPT) => + val command = args.mkString(" ") + if commonOptions.verbose then { println(s"Running command: $command") } + val result = Timer.time(command, show = commonOptions.showTimes, log) { + run(opts, commonOptions, log, outputDirOverride) + } + result + case None => Left(errors(s"Failed to parse $pluginName options")) + end match + end run + + private type OptionPlacer[V] = (V, OPT) => OPT + protected val builder: OParserBuilder[OPT] = OParser.builder[OPT] + import builder.* + + def inputFile(f: OptionPlacer[File]): OParser[File, OPT] = + arg[File]("input-file") + .required() + .action((v, c) => f(v, c)) + .text("required riddl input file to read") + + def outputDir(f: OptionPlacer[File]): OParser[File, OPT] = + opt[File]('o', "output-dir") + .optional() + .action((v, c) => f(v, c)) + .text("required output directory for the generated output") + + protected def replaceInputFile( + options: OPT, + @unused inputFile: Path + ): OPT = options + + def resolveInputFileToConfigFile( + options: OPT, + commonOptions: CommonOptions, + configFile: Path + ): OPT = + options.inputFile match + case Some(inFile) => + val parent = Option(configFile.getParent) match + case Some(path) => path + case None => Path.of(".") + val input = parent.resolve(inFile) + val result = replaceInputFile(options, input) + if commonOptions.debug then + val pretty = StringHelpers.toPrettyString( + result, + 1, + Some(s"Loaded these options:${System.lineSeparator()}") + ) + println(pretty) + end if + result + case None => options + end match + end resolveInputFileToConfigFile +end Command diff --git a/command/src/main/scala/com/ossuminc/riddl/command/CommandOptions.scala b/command/src/main/scala/com/ossuminc/riddl/command/CommandOptions.scala index f3c8b9047..b954ef397 100644 --- a/command/src/main/scala/com/ossuminc/riddl/command/CommandOptions.scala +++ b/command/src/main/scala/com/ossuminc/riddl/command/CommandOptions.scala @@ -3,16 +3,12 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package com.ossuminc.riddl.command + import com.ossuminc.riddl.language.Messages -import com.ossuminc.riddl.language.Messages.Messages -import com.ossuminc.riddl.language.Messages.errors -import com.ossuminc.riddl.utils.Plugin +import com.ossuminc.riddl.language.Messages.{Messages, errors} +import pureconfig.{ConfigCursor, ConfigObjectCursor, ConfigReader} import pureconfig.error.ConfigReaderFailures -import pureconfig.ConfigCursor -import pureconfig.ConfigObjectCursor -import pureconfig.ConfigReader import scopt.OParser import java.nio.file.Path @@ -29,7 +25,7 @@ trait CommandOptions { ): Either[Messages, S] = { CommandOptions.withInputFile(inputFile, command)(f) } - + def check: Messages = { if inputFile.isEmpty then { Messages.errors("An input path was not provided.") @@ -58,31 +54,7 @@ object CommandOptions { def command: String = "unspecified" def inputFile: Option[Path] = Option.empty[Path] } - - def parseCommandOptions( - args: Array[String] - ): Either[Messages, CommandOptions] = { - require(args.nonEmpty) - val result = CommandPlugin.loadCommandNamed(args.head) - result match { - case Right(cmd) => - cmd.parseOptions(args) match { - case Some(options) => Right(options) - case None => Left(errors("RiddlOption parsing failed")) - } - case Left(messages) => Left(messages) - } - } - - val commandOptionsParser: OParser[Unit, CommandOptions] = { - val plugins = Plugin.loadPluginsFrom[CommandPlugin[CommandOptions]]() - val list = - for plugin <- plugins yield { plugin.pluginName -> plugin.getOptions } - val parsers = list.sortBy(_._1).map(_._2._1) // alphabetize - require(parsers.nonEmpty, "No command line options parsers!") - OParser.sequence(parsers.head, parsers.drop(1)*) - } - + /** A helper function for reading optional items from a config file. * * @param objCur diff --git a/command/src/main/scala/com/ossuminc/riddl/command/CommandPlugin.scala b/command/src/main/scala/com/ossuminc/riddl/command/CommandPlugin.scala deleted file mode 100644 index 954048596..000000000 --- a/command/src/main/scala/com/ossuminc/riddl/command/CommandPlugin.scala +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright 2019 Ossum, Inc. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.ossuminc.riddl.command - -import com.ossuminc.riddl.language.{At, CommonOptions} -import com.ossuminc.riddl.language.Messages -import com.ossuminc.riddl.language.Messages.Messages -import com.ossuminc.riddl.language.Messages.SevereError -import com.ossuminc.riddl.language.Messages.errors -import com.ossuminc.riddl.language.Messages.highestSeverity -import com.ossuminc.riddl.language.Messages.severes -import com.ossuminc.riddl.utils.* -import com.ossuminc.riddl.passes.PassesResult -import pureconfig.ConfigReader -import pureconfig.ConfigSource -import scopt.OParser -import scopt.OParserBuilder - -import java.io.File -import java.nio.file.Path -import scala.annotation.unused -import scala.jdk.CollectionConverters.CollectionHasAsScala -import scala.reflect.ClassTag -import scala.reflect.classTag -import scala.util.control.NonFatal - -object CommandPlugin { - def loadCommandNamed( - name: String, - commonOptions: CommonOptions = CommonOptions(), - pluginsDir: Path = Plugin.pluginsDir - ): Either[Messages, CommandPlugin[CommandOptions]] = { - if commonOptions.verbose then { println(s"Loading command: $name") } - val loaded = Plugin.loadPluginsFrom[CommandPlugin[CommandOptions]](pluginsDir) - if loaded.isEmpty then { Left(errors(s"No command found for '$name'")) } - else { - loaded.find(_.pluginName == name) match { - case Some(pl) if pl.isInstanceOf[CommandPlugin[CommandOptions]] => - Right(pl) - case Some(plugin) => - Left( - errors( - s"Plugin for command $name is the wrong type ${plugin.getClass.getSimpleName}" - ) - ) - case None => Left(errors(s"No plugin command matches '$name'")) - } - } - } - - private def runCommandWithArgs( - name: String, - args: Array[String], - log: Logger, - commonOptions: CommonOptions, - pluginsDir: Path = Plugin.pluginsDir - ): Either[Messages, PassesResult] = { - val result = loadCommandNamed(name, commonOptions, pluginsDir) - .flatMap { cmd => cmd.run(args, commonOptions, log) } - if commonOptions.verbose then { - val rc = if result.isRight then "yes" else "no" - println(s"Ran: ${args.mkString(" ")}: success=$rc") - } - result - } - - def runCommandNamed( - name: String, - optionsPath: Path, - log: Logger, - commonOptions: CommonOptions = CommonOptions(), - pluginsDir: Path = Plugin.pluginsDir, - outputDirOverride: Option[Path] = None - ): Either[Messages, PassesResult] = { - if commonOptions.verbose then { - println(s"About to run $name with options from $optionsPath") - } - loadCommandNamed(name, commonOptions, pluginsDir).flatMap { cmd => - cmd.loadOptionsFrom(optionsPath, commonOptions).flatMap { opts => - cmd.run(opts, commonOptions, log, outputDirOverride) match { - case Left(errors) => - if commonOptions.debug then { - println(s"Errors after running '$name':") - println(errors.format) - } - Left(errors) - case Right(passesResult) => Right(passesResult) - } - } - } - } - - def loadCandidateCommands( - configFile: Path, - commonOptions: CommonOptions = CommonOptions() - ): Either[Messages, Seq[String]] = { - val names = ConfigSource - .file(configFile.toFile) - .value() - .map(_.keySet().asScala.toSeq) - names match { - case Right(value) => - if commonOptions.verbose then { - println( - s"Found candidate commands in $configFile: ${value.mkString(" ")}" - ) - } - Right(value) - case Left(fails) => - val message = s"Errors while reading $configFile:\n" + - fails.prettyPrint(1) - Left(errors(message)) - } - } - - def runFromConfig( - configFile: Option[Path], - targetCommand: String, - commonOptions: CommonOptions, - log: Logger, - commandName: String - ): Either[Messages, PassesResult] = { - val result = CommandOptions.withInputFile[PassesResult](configFile, commandName) { path => - CommandPlugin - .loadCandidateCommands(path, commonOptions) - .flatMap { names => - if names.contains(targetCommand) then { - CommandPlugin - .runCommandNamed(targetCommand, path, log, commonOptions) match { - case Left(errors) => - if commonOptions.debug then { - println(s"Errors after running `$targetCommand`:") - println(errors.format) - } - Left(errors) - case result: Right[Messages, PassesResult] => result - } - } else { - Left[Messages, PassesResult]( - errors( - s"Command '$targetCommand' is not defined in $path" - ) - ) - } - } - } - handleCommandResult(result, commonOptions, log) - result - } - - private def handleCommandResult( - result: Either[Messages, PassesResult], - commonOptions: CommonOptions, - log: Logger - ): Int = { - result match { - case Right(passesResult: PassesResult) => - if passesResult.commonOptions.quiet then { - System.out.println(log.summary) - } else { - Messages.logMessages(passesResult.messages, log, passesResult.commonOptions) - } - if passesResult.commonOptions.warningsAreFatal && passesResult.messages.hasWarnings then 1 - else 0 - case Left(messages) => - if commonOptions.quiet then { highestSeverity(messages) + 1 } - else { Messages.logMessages(messages, log, commonOptions) + 1 } - } - } - - private def handleCommandRun( - remaining: Array[String], - commonOptions: CommonOptions - ): Int = { - val log: Logger = - if commonOptions.quiet then StringLogger() - else SysLogger() - - if remaining.isEmpty then - log.error("No command argument was provided") - 1 - else - val name = remaining.head - if commonOptions.dryRun then - log.info(s"Would have executed: ${remaining.mkString(" ")}") - 0 - else - val result = CommandPlugin.runCommandWithArgs(name, remaining, log, commonOptions) - handleCommandResult(result, commonOptions, log) - } - - def runMainForTest(args: Array[String]): Either[Messages,PassesResult] = { - try { - val (common, remaining) = CommonOptionsHelper.parseCommonOptions(args) - common match - case Some(commonOptions) => - val log: Logger = if commonOptions.quiet then StringLogger() else SysLogger() - if remaining.isEmpty then - Left(List(Messages.error("No command argument was provided"))) - else - val name = remaining.head - CommandPlugin.runCommandWithArgs(name, remaining, log, commonOptions) - case None => - Left(List(Messages.error("Option parsing failed, terminating."))) - } catch { - case NonFatal(exception) => - Left(List(Messages.severe("Exception Thrown:", exception, At.empty))) - } - } - - def runMain(args: Array[String], log: Logger = SysLogger()): Int = { - try { - val (common, remaining) = CommonOptionsHelper.parseCommonOptions(args) - common match { - case Some(commonOptions) => - handleCommandRun(remaining, commonOptions) - case None => - // arguments are bad, error message will have been displayed - log.info("Option parsing failed, terminating.") - 1 - } - } catch { - case NonFatal(exception) => - log.severe("Exception Thrown:", exception) - SevereError.severity + 1 - } - } -} - -/** The service interface for Riddlc command plugins */ -trait CommandPlugin[OPT <: CommandOptions: ClassTag](val pluginName: String) extends PluginInterface { - final override def pluginVersion: String = RiddlBuildInfo.version - - private val optionsClass: Class[?] = classTag[OPT].runtimeClass - - /** Provide an scopt OParser for the commands options type, OPT - * @return - * A pair: the OParser and the default values for OPT - */ - def getOptions: (OParser[Unit, OPT], OPT) - - def parseOptions( - args: Array[String] - ): Option[OPT] = { - val (parser, default) = getOptions - val (result, effects) = OParser.runParser(parser, args, default) - OParser.runEffects(effects) - result - } - - /** Provide a typesafe/Config reader for the commands options. This reader should read an object having the same name - * as the command. The fields of that object must correspond to the fields of the OPT type. - * @return - * A pureconfig.ConfigReader[OPT] that knows how to read OPT - */ - def getConfigReader: ConfigReader[OPT] - - def loadOptionsFrom( - configFile: Path, - commonOptions: CommonOptions = CommonOptions() - ): Either[Messages, OPT] = { - if commonOptions.verbose then { - println(s"Reading command options from: $configFile") - } - ConfigSource.file(configFile).load[OPT](getConfigReader) match { - case Right(value) => - if commonOptions.verbose then { - println(s"Read command options from $configFile") - } - if commonOptions.debug then { - println(StringHelpers.toPrettyString(value, 1)) - } - Right(value) - case Left(fails) => - Left( - errors( - s"Errors while reading $configFile:\n" + fails.prettyPrint(1) - ) - ) - } - } - - /** Execute the command given the options. Error should be returned as Left(messages) and not directly logged. The log - * is for verbose or debug output - * - * @param options - * The command specific options - * @param commonOptions - * The options common to all commands - * @param log - * A logger for logging errors, warnings, and info - * @return - * Either a set of Messages on error or a Unit on success - */ - def run( - @unused options: OPT, - @unused commonOptions: CommonOptions, - @unused log: Logger, - @unused outputDirOverride: Option[Path] - ): Either[Messages, PassesResult] = { - Left( - severes( - s"""In command '$pluginName': - |the CommandPlugin.run(OPT,CommonOptions,Logger) method was not overridden""".stripMargin - ) - ) - } - - def run( - args: Array[String], - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] = None - ): Either[Messages, PassesResult] = { - val maybeOptions: Option[OPT] = parseOptions(args) - maybeOptions match { - case Some(opts: OPT) => - val command = args.mkString(" ") - if commonOptions.verbose then { println(s"Running command: $command") } - val result = Timer.time(command, show = commonOptions.showTimes, log) { - run(opts, commonOptions, log, outputDirOverride) - } - result - case None => Left(errors(s"Failed to parse $pluginName options")) - } - } - - private type OptionPlacer[V] = (V, OPT) => OPT - protected val builder: OParserBuilder[OPT] = OParser.builder[OPT] - import builder.* - - def inputFile(f: OptionPlacer[File]): OParser[File, OPT] = { - arg[File]("input-file") - .required() - .action((v, c) => f(v, c)) - .text("required riddl input file to read") - } - - def outputDir(f: OptionPlacer[File]): OParser[File, OPT] = { - opt[File]('o', "output-dir") - .optional() - .action((v, c) => f(v, c)) - .text("required output directory for the generated output") - } - - def replaceInputFile( - options: OPT, - @unused inputFile: Path - ): OPT = options - - def resolveInputFileToConfigFile( - options: OPT, - commonOptions: CommonOptions, - configFile: Path - ): OPT = { - options.inputFile match { - case Some(inFile) => - val parent = Option(configFile.getParent) match { - case Some(path) => path - case None => Path.of(".") - } - val input = parent.resolve(inFile) - val result = replaceInputFile(options, input) - if commonOptions.debug then { - val pretty = StringHelpers.toPrettyString( - result, - 1, - Some(s"Loaded these options:${System.lineSeparator()}") - ) - println(pretty) - } - result - case None => options - } - } -} diff --git a/command/src/main/scala/com/ossuminc/riddl/command/CommonOptionsHelper.scala b/command/src/main/scala/com/ossuminc/riddl/command/CommonOptionsHelper.scala index 787c1d4b1..b34199d7b 100644 --- a/command/src/main/scala/com/ossuminc/riddl/command/CommonOptionsHelper.scala +++ b/command/src/main/scala/com/ossuminc/riddl/command/CommonOptionsHelper.scala @@ -3,30 +3,21 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package com.ossuminc.riddl.command -import com.ossuminc.riddl.language.{CommonOptions, Messages} +import com.ossuminc.riddl.command.CommandOptions.optional import com.ossuminc.riddl.language.Messages.* +import com.ossuminc.riddl.language.{CommonOptions, Messages} import com.ossuminc.riddl.utils.StringHelpers.toPrettyString import com.ossuminc.riddl.utils.{RiddlBuildInfo, SysLogger} -import com.ossuminc.riddl.command.CommandOptions.optional -import scopt.DefaultOEffectSetup -import scopt.DefaultOParserSetup -import scopt.OParser -import scopt.OParserBuilder -import scopt.OParserSetup -import scopt.RenderingMode +import pureconfig.{ConfigCursor, ConfigObjectCursor, ConfigReader, ConfigSource} import pureconfig.error.ConfigReaderFailures -import pureconfig.ConfigCursor -import pureconfig.ConfigObjectCursor -import pureconfig.ConfigReader -import pureconfig.ConfigSource +import scopt.* -import scala.concurrent.duration.FiniteDuration import java.io.File import java.nio.file.Path import java.util.Calendar +import scala.concurrent.duration.FiniteDuration /** Handle processing of Language module's CommonOptions */ object CommonOptionsHelper { @@ -290,7 +281,7 @@ object CommonOptionsHelper { ) } }} - + final def loadCommonOptions( path: Path ): Either[Messages, CommonOptions] = { diff --git a/command/src/main/scala/com/ossuminc/riddl/command/InputFileCommandPlugin.scala b/command/src/main/scala/com/ossuminc/riddl/command/InputFileCommandPlugin.scala index 8aafa94eb..25de941c9 100644 --- a/command/src/main/scala/com/ossuminc/riddl/command/InputFileCommandPlugin.scala +++ b/command/src/main/scala/com/ossuminc/riddl/command/InputFileCommandPlugin.scala @@ -5,6 +5,7 @@ */ package com.ossuminc.riddl.command + import pureconfig.ConfigCursor import pureconfig.ConfigReader import scopt.OParser @@ -20,7 +21,7 @@ object InputFileCommandPlugin { * @param name * The name of the command */ -abstract class InputFileCommandPlugin(name: String) extends CommandPlugin[InputFileCommandPlugin.Options](name) { +abstract class InputFileCommandPlugin(name: String) extends Command[InputFileCommandPlugin.Options](name) { import InputFileCommandPlugin.Options def getOptions: (OParser[Unit, Options], Options) = { import builder.* diff --git a/command/src/main/scala/com/ossuminc/riddl/command/PassCommand.scala b/command/src/main/scala/com/ossuminc/riddl/command/PassCommand.scala index e05203958..357627785 100644 --- a/command/src/main/scala/com/ossuminc/riddl/command/PassCommand.scala +++ b/command/src/main/scala/com/ossuminc/riddl/command/PassCommand.scala @@ -3,13 +3,12 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package com.ossuminc.riddl.command -import com.ossuminc.riddl.language.{CommonOptions, Messages} -import com.ossuminc.riddl.language.parsing.TopLevelParser import com.ossuminc.riddl.language.Messages.Messages -import com.ossuminc.riddl.passes.{Pass, PassInput, PassesResult, PassesCreator} +import com.ossuminc.riddl.language.parsing.TopLevelParser +import com.ossuminc.riddl.language.{CommonOptions, Messages} +import com.ossuminc.riddl.passes.{Pass, PassInput, PassesCreator, PassesResult} import com.ossuminc.riddl.utils.Logger import java.nio.file.Path @@ -31,11 +30,11 @@ trait PassCommandOptions extends CommandOptions { /** An abstract base class for commands that use passes. * * @param name - * The name of the command to pass to [[CommandPlugin]] + * The name of the command to pass to [[Command]] * @tparam OPT * The option type for the command */ -abstract class PassCommand[OPT <: PassCommandOptions: ClassTag](name: String) extends CommandPlugin[OPT](name) { +abstract class PassCommand[OPT <: PassCommandOptions: ClassTag](name: String) extends Command[OPT](name) { def getPasses( log: Logger, @@ -51,7 +50,9 @@ abstract class PassCommand[OPT <: PassCommandOptions: ClassTag](name: String) ex log: Logger ): Either[Messages, PassesResult] = { options.withInputFile { (inputPath: Path) => - TopLevelParser.parsePath(inputPath, commonOptions) match { + import com.ossuminc.riddl.language.parsing.RiddlParserInput + val rpi = RiddlParserInput.fromPath(inputPath) + TopLevelParser.parseInput(rpi, commonOptions) match { case Left(errors) => Left[Messages, PassesResult](errors) case Right(root) => diff --git a/command/src/main/scala/com/ossuminc/riddl/command/TranslationCommand.scala b/command/src/main/scala/com/ossuminc/riddl/command/TranslationCommand.scala index ff3693c93..6c5c939b7 100644 --- a/command/src/main/scala/com/ossuminc/riddl/command/TranslationCommand.scala +++ b/command/src/main/scala/com/ossuminc/riddl/command/TranslationCommand.scala @@ -3,7 +3,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package com.ossuminc.riddl.command import com.ossuminc.riddl.language.AST.* @@ -29,7 +28,7 @@ object TranslationCommand { * directory of files. * * @param name - * The name of the command to pass to [[CommandPlugin]] + * The name of the command to pass to [[Command]] * @tparam OPT * The option type for the command */ diff --git a/command/src/test/input/cmdoptions.conf b/command/src/test/input/cmdoptions.conf new file mode 100644 index 000000000..3904c25d8 --- /dev/null +++ b/command/src/test/input/cmdoptions.conf @@ -0,0 +1,31 @@ +common { + show-times = true + verbose = false + quiet = false + debug = true + dry-run = false + show-warnings = true + show-missing-warnings = false + show-style-warnings = false +} + +dump { input-file = "dump.riddl" } + +from { config-file = "file.conf" + target-command = "dump" +} + +onchange { + config-file = "commands/src/test/input/onchange.riddl" + watch-directory = "commands/src/test/input" + target-command = "parse" + interactive = true +} + +parse { input-file = "parse.riddl" } + +stats { + input-file = "stats.riddl" +} + +validate { input-file = "validate.riddl" } diff --git a/command/src/test/input/onchangevalidation.conf b/command/src/test/input/onchangevalidation.conf new file mode 100644 index 000000000..90ce65451 --- /dev/null +++ b/command/src/test/input/onchangevalidation.conf @@ -0,0 +1,13 @@ + +onchange { + config-file = "" + watch-directory = "" + target-command = "" + interactive = true +} + +parse { input-file = "parse.riddl" } + +stats { input-file = "stats.riddl" } + +validate { input-file = "validate.riddl" } diff --git a/command/src/test/input/regressions/match-error.riddl b/command/src/test/input/regressions/match-error.riddl new file mode 100644 index 000000000..b5037d563 --- /dev/null +++ b/command/src/test/input/regressions/match-error.riddl @@ -0,0 +1,25 @@ +domain PersonalBanking is { + user Member is "A person holding an account at Personal Bank" + + epic CreateAccount is { + user Member wants to "establish an account" so that "they can apply for a loan" + case HappyPath { + step show output PersonalBanking.PersonalBankingApp.Test.HomePage.AccountDetailsPage + to user PersonalBanking.Member + } + } + + domain PersonalBankingApp is { + application Test is { + option is technology("react.js") + result Title is { value: String } + command Name is { value: String } + group HomePage is { + output AccountDetailsPage presents result Title + described as "Show a blank page with a title" + input Two acquires command Name + described as "Show a blank page with a title, collect a Name" + } + } described as "A very simple app for testing" + } briefly "The user interface for the Personal Banking Application" +} diff --git a/command/src/test/input/repeat-options.conf b/command/src/test/input/repeat-options.conf new file mode 100644 index 000000000..7ef930847 --- /dev/null +++ b/command/src/test/input/repeat-options.conf @@ -0,0 +1,19 @@ +# options for hugo-translator +command = hugo +common = { + show-times = true + verbose = true + quiet = false + dry-run = false + suppress-warnings = false + suppress-missing-warnings = true + suppress-style-warnings = true +} +repeat { + input-file = "ReactiveBBQ.conf" + target-command = "from" + refresh-rate = 5.seconds + max-cycles = 10 + interactive = true +} + diff --git a/command/src/test/resources/META-INF/services/com.ossuminc.riddl.commands.CommandPlugin b/command/src/test/resources/META-INF/services/com.ossuminc.riddl.commands.CommandPlugin deleted file mode 100644 index e3ec8e428..000000000 --- a/command/src/test/resources/META-INF/services/com.ossuminc.riddl.commands.CommandPlugin +++ /dev/null @@ -1 +0,0 @@ -com.ossuminc.riddl.command.ASimpleTestCommand diff --git a/command/src/test/scala/com/ossuminc/riddl/command/CommandPluginTest.scala b/command/src/test/scala/com/ossuminc/riddl/command/CommandPluginTest.scala deleted file mode 100644 index 42786b618..000000000 --- a/command/src/test/scala/com/ossuminc/riddl/command/CommandPluginTest.scala +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2019 Ossum, Inc. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.ossuminc.riddl.command - -/** Unit Tests For Running Riddlc Commands from Plugins */ - -import com.ossuminc.riddl.utils.{Plugin, PluginSpecBase} - -import java.nio.file.Path -import scopt.* -import pureconfig.* - -class CommandPluginTest - extends PluginSpecBase( - svcClassPath = Path.of("com/ossuminc/riddl/command/CommandPlugin.class"), - implClassPath = Path - .of("com/ossuminc/riddl/command/ASimpleTestCommand.class"), - moduleName = "command", - jarFilename = "test-command.jar" - ) { - - "CommandPlugin " should { - "get options from command line" in { - val plugins = Plugin - .loadPluginsFrom[CommandPlugin[CommandOptions]](tmpDir) - plugins must not(be(empty)) - val p = plugins.head - p.getClass must be(classOf[ASimpleTestCommand]) - val plugin = p.asInstanceOf[ASimpleTestCommand] - val args: Seq[String] = Seq("test", "input-file", "Success!") - val (parser, default) = plugin.getOptions - OParser.parse(parser, args, default) match { - case Some(to) => - to.command must be("test") - to.inputFile.get.toString must be("input-file") - to.arg1 must be("Success!") - case None => - fail("No options returned from OParser.parse") - } - } - "get options from config file" in { - val plugins = Plugin - .loadPluginsFrom[CommandPlugin[CommandOptions]](tmpDir) - plugins must not(be(empty)) - val p = plugins.head - p.getClass must be(classOf[ASimpleTestCommand]) - val plugin = p.asInstanceOf[ASimpleTestCommand] - val reader = plugin.getConfigReader - val path: Path = Path.of("command/src/test/input/test.conf") - ConfigSource - .file(path.toFile) - .load[ASimpleTestCommand.Options](reader) match { - case Right(loadedOptions) => loadedOptions.arg1 mustBe "Success!" - case Left(failures) => fail(failures.prettyPrint()) - } - } - - "run a command via a plugin" in { - val args = Array(s"--plugins-dir=${tmpDir.toString}", "test", "inputfile", "test") - CommandPlugin.runMain(args) mustBe 0 - } - - "handle wrong file as input" in { - val args = Array( - "--verbose", - "--suppress-style-warnings", - "--suppress-missing-warnings", - "test", - "command/src/test/input/foo.riddl", // wrong file! - "hugo" - ) - val rc = CommandPlugin.runMain(args) - rc must be(0) - } - - "handle wrong command as target" in { - val args = Array( - "--verbose", - "--suppress-style-warnings", - "--suppress-missing-warnings", - "test", - "command/src/test/input/repeat-options.conf", - "flumox" // unknown command - ) - val rc = CommandPlugin.runMain(args) - rc must be(0) - } - } -} diff --git a/commands/src/main/resources/META-INF/services/com.ossuminc.riddl.command.CommandPlugin b/commands/src/main/resources/META-INF/services/com.ossuminc.riddl.command.CommandPlugin deleted file mode 100644 index a5a4b2f78..000000000 --- a/commands/src/main/resources/META-INF/services/com.ossuminc.riddl.command.CommandPlugin +++ /dev/null @@ -1,13 +0,0 @@ -com.ossuminc.riddl.commands.AboutCommand -com.ossuminc.riddl.commands.DumpCommand -com.ossuminc.riddl.commands.FromCommand -com.ossuminc.riddl.commands.HelpCommand -com.ossuminc.riddl.commands.HugoCommand -com.ossuminc.riddl.commands.InfoCommand -com.ossuminc.riddl.commands.OnChangeCommand -com.ossuminc.riddl.commands.ParseCommand -com.ossuminc.riddl.commands.PrettifyCommand -com.ossuminc.riddl.commands.RepeatCommand -com.ossuminc.riddl.commands.StatsCommand -com.ossuminc.riddl.commands.ValidateCommand -com.ossuminc.riddl.commands.VersionCommand diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/AboutCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/AboutCommand.scala index 1da5fb7ee..39388b6e3 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/AboutCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/AboutCommand.scala @@ -6,13 +6,12 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandOptions -import com.ossuminc.riddl.command.CommandPlugin -import com.ossuminc.riddl.command.CommonOptionsHelper import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.PassesResult import com.ossuminc.riddl.utils.Logger +import com.ossuminc.riddl.command.{Command, CommandOptions,CommonOptionsHelper} + import pureconfig.ConfigCursor import pureconfig.ConfigReader @@ -29,9 +28,9 @@ object AboutCommand { extends CommandOptions } -class AboutCommand extends CommandPlugin[AboutCommand.Options]("about") { +class AboutCommand extends Command[AboutCommand.Options]("about") { import AboutCommand.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { import builder.* cmd(pluginName).action((_, c) => c.copy(command = pluginName)) .text("Print out information about RIDDL") -> AboutCommand.Options() @@ -47,10 +46,10 @@ class AboutCommand extends CommandPlugin[AboutCommand.Options]("about") { } override def run( - options: AboutCommand.Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: AboutCommand.Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { if commonOptions.verbose || !commonOptions.quiet then { val about: String = { diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/Commands.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/Commands.scala new file mode 100644 index 000000000..2597a169d --- /dev/null +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/Commands.scala @@ -0,0 +1,243 @@ +package com.ossuminc.riddl.commands + +import com.ossuminc.riddl.utils.{Logger, StringLogger, SysLogger} +import com.ossuminc.riddl.language.{At, CommonOptions} +import com.ossuminc.riddl.language.Messages.* +import com.ossuminc.riddl.passes.PassesResult +import com.ossuminc.riddl.command.{Command, CommandOptions, CommonOptionsHelper} +import pureconfig.error.ConfigReaderFailures + +import java.nio.file.Path +import pureconfig.{ConfigCursor, ConfigObjectCursor, ConfigReader, ConfigSource} +import scala.util.control.NonFatal +import scala.jdk.CollectionConverters.CollectionHasAsScala + +object Commands: + def loadCommandNamed( + name: String, + commonOptions: CommonOptions = CommonOptions() + ): Either[Messages, Command[?]] = + if commonOptions.verbose then + println(s"Loading command: $name") + end if + name match + case "about" => Right(AboutCommand()) + case "dump" => Right(DumpCommand()) + case "flatten" => Right(FlattenCommand()) + case "from" => Right(FromCommand()) + case "help" => Right(HelpCommand()) + case "hugo" => Right(HugoCommand()) + case "info" => Right(InfoCommand()) + case "onchange" => Right(OnChangeCommand()) + case "parse" => Right(ParseCommand()) + case "prettify" => Right(PrettifyCommand()) + case "repeat" => Right(RepeatCommand()) + case "stats" => Right(StatsCommand()) + case "validate" => Right(ValidateCommand()) + case "version" => Right(VersionCommand()) + case _ => Left(errors(s"No command found for '$name'")) + end match + end loadCommandNamed + + def runCommandWithArgs( + name: String, + args: Array[String], + log: Logger, + commonOptions: CommonOptions + ): Either[Messages, PassesResult] = + val result = loadCommandNamed(name, commonOptions) + .flatMap { cmd => cmd.run(args, commonOptions, log) } + if commonOptions.verbose then + val rc = if result.isRight then "yes" else "no" + println(s"Ran: ${args.mkString(" ")}: success=$rc") + end if + result + end runCommandWithArgs + + def runCommandNamed( + name: String, + optionsPath: Path, + log: Logger, + commonOptions: CommonOptions = CommonOptions(), + outputDirOverride: Option[Path] = None + ): Either[Messages, PassesResult] = + if commonOptions.verbose then + println(s"About to run $name with options from $optionsPath") + end if + loadCommandNamed(name, commonOptions).flatMap { cmd => + cmd.loadOptionsFrom(optionsPath, commonOptions).flatMap { opts => + cmd.run(opts, commonOptions, log, outputDirOverride) match + case Left(errors) => + if commonOptions.debug then { + println(s"Errors after running '$name':") + println(errors.format) + } + Left(errors) + case Right(passesResult) => Right(passesResult) + end match + } + } + end runCommandNamed + + def loadCandidateCommands( + configFile: Path, + commonOptions: CommonOptions = CommonOptions() + ): Either[Messages, Seq[String]] = + val names = ConfigSource + .file(configFile.toFile) + .value() + .map(_.keySet().asScala.toSeq) + names match + case Right(value) => + if commonOptions.verbose then + println(s"Found candidate commands in $configFile: ${value.mkString(" ")}") + Right(value) + case Left(fails) => + val message = s"Errors while reading $configFile:\n" + fails.prettyPrint(1) + Left(errors(message)) + end match + end loadCandidateCommands + + def runFromConfig( + configFile: Option[Path], + targetCommand: String, + commonOptions: CommonOptions, + log: Logger, + commandName: String + ): Either[Messages, PassesResult] = + val result = CommandOptions.withInputFile[PassesResult](configFile, commandName) { path => + loadCandidateCommands(path, commonOptions).flatMap { names => + if names.contains(targetCommand) then + runCommandNamed(targetCommand, path, log, commonOptions) match + case Left(errors) => + if commonOptions.debug then + println(s"Errors after running `$targetCommand`:") + println(errors.format) + Left(errors) + case result: Right[Messages, PassesResult] => result + end match + else + Left[Messages, PassesResult](errors(s"Command '$targetCommand' is not defined in $path")) + end if + } + } + handleCommandResult(result, commonOptions, log) + result + end runFromConfig + + private def handleCommandResult( + result: Either[Messages, PassesResult], + commonOptions: CommonOptions, + log: Logger + ): Int = + result match + case Right(passesResult: PassesResult) => + if passesResult.commonOptions.quiet then + System.out.println(log.summary) + else + logMessages(passesResult.messages, log, passesResult.commonOptions) + if passesResult.commonOptions.warningsAreFatal && passesResult.messages.hasWarnings then 1 else 0 + case Left(messages) => + if commonOptions.quiet then highestSeverity(messages) + 1 + else { + logMessages(messages, log, commonOptions) + 1 + } + end match + end handleCommandResult + + private def handleCommandRun( + remaining: Array[String], + commonOptions: CommonOptions, + log: Logger + ): Int = + if remaining.isEmpty then + log.error("No command argument was provided") + 1 + else + val name = remaining.head + if commonOptions.dryRun then + log.info(s"Would have executed: ${remaining.mkString(" ")}") + 0 + else + val result = runCommandWithArgs(name, remaining, log, commonOptions) + handleCommandResult(result, commonOptions, log) + end handleCommandRun + + def runMainForTest(args: Array[String]): Either[Messages, PassesResult] = + try + val (common, remaining) = CommonOptionsHelper.parseCommonOptions(args) + common match + case Some(commonOptions) => + val log: Logger = if commonOptions.quiet then StringLogger() else SysLogger() + if remaining.isEmpty then + Left(List(error("No command argument was provided"))) + else + val name = remaining.head + runCommandWithArgs(name, remaining, log, commonOptions) + case None => + Left(List(error("Option parsing failed, terminating."))) + end match + catch + case NonFatal(exception) => + Left(List(severe("Exception Thrown:", exception, At.empty))) + end runMainForTest + + def runMain(args: Array[String], log: Logger = SysLogger()): Int = + try + val (common, remaining) = CommonOptionsHelper.parseCommonOptions(args) + common match + case Some(commonOptions) => + handleCommandRun(remaining, commonOptions, log) + case None => + // arguments are bad, error message will have been displayed + log.info("Option parsing failed, terminating.") + 1 + end match + catch + case NonFatal(exception) => + log.severe("Exception Thrown:", exception) + SevereError.severity + 1 + end runMain + + def parseCommandOptions( + args: Array[String] + ): Either[Messages, CommandOptions] = + require(args.nonEmpty) + val result = loadCommandNamed(args.head) + result match + case Right(cmd) => + cmd.parseOptions(args) match { + case Some(options) => Right(options) + case None => Left(errors("RiddlOption parsing failed")) + } + case Left(messages) => Left(messages) + end match + end parseCommandOptions + + /** A helper function for reading optional items from a config file. + * + * @param objCur + * The ConfigObjectCursor to start with + * @param key + * The name of the optional config item + * @param default + * The default value of the config item + * @param mapIt + * The function to map ConfigCursor to ConfigReader.Result[T] + * @tparam T + * The Scala type of the config item's value + * @return + * The reader for this optional configuration item. + */ + def optional[T]( + objCur: ConfigObjectCursor, + key: String, + default: T + )(mapIt: ConfigCursor => ConfigReader.Result[T]): ConfigReader.Result[T] = + objCur.atKeyOrUndefined(key) match + case stCur if stCur.isUndefined => Right[ConfigReaderFailures, T](default) + case stCur => mapIt(stCur) + end match + end optional +end Commands + diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/DumpCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/DumpCommand.scala index 066d92525..335834788 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/DumpCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/DumpCommand.scala @@ -8,9 +8,10 @@ package com.ossuminc.riddl.commands import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.language.CommonOptions +import com.ossuminc.riddl.language.parsing.RiddlParserInput import com.ossuminc.riddl.passes.{PassesResult, Riddl} -import com.ossuminc.riddl.utils.{Logger,StringHelpers} -import com.ossuminc.riddl.command.InputFileCommandPlugin +import com.ossuminc.riddl.utils.{Logger, StringHelpers} +import com.ossuminc.riddl.command.Command import java.nio.file.Path @@ -20,8 +21,8 @@ object DumpCommand { /** A Command for Parsing RIDDL input */ -class DumpCommand extends InputFileCommandPlugin(DumpCommand.cmdName) { - import InputFileCommandPlugin.Options +class DumpCommand extends InputFileCommand(DumpCommand.cmdName) { + import InputFileCommand.Options override def run( options: Options, @@ -30,7 +31,8 @@ class DumpCommand extends InputFileCommandPlugin(DumpCommand.cmdName) { outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { options.withInputFile { (inputFile: Path) => - Riddl.parseAndValidate(inputFile, commonOptions).map { result => + val rpi = RiddlParserInput.fromCwdPath(inputFile) + Riddl.parseAndValidate(rpi, commonOptions).map { result => log.info(s"AST of $inputFile is:") log.info(StringHelpers.toPrettyString(result, 1, None)) result @@ -38,11 +40,6 @@ class DumpCommand extends InputFileCommandPlugin(DumpCommand.cmdName) { } } - override def replaceInputFile( - opts: Options, - inputFile: Path - ): Options = { opts.copy(inputFile = Some(inputFile)) } - override def loadOptionsFrom( configFile: Path, commonOptions: CommonOptions diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/FlattenCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/FlattenCommand.scala new file mode 100644 index 000000000..6cecfc2b2 --- /dev/null +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/FlattenCommand.scala @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Ossum, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ossuminc.riddl.commands + +import com.ossuminc.riddl.language.CommonOptions +import com.ossuminc.riddl.language.Messages.Messages +import com.ossuminc.riddl.language.parsing.RiddlParserInput +import com.ossuminc.riddl.passes.{PassesResult, Riddl} +import com.ossuminc.riddl.utils.{Logger, StringHelpers} + +import java.nio.file.Path + +object FlattenCommand { + final val cmdName = "flatten" +} + +/** A Command for Parsing RIDDL input + */ +class FlattenCommand extends InputFileCommand(DumpCommand.cmdName) { + import InputFileCommand.Options + + override def run( + options: Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] + ): Either[Messages, PassesResult] = { + options.withInputFile { (inputFile: Path) => + val rpi = RiddlParserInput.fromCwdPath(inputFile) + Riddl.parseAndValidate(rpi, commonOptions).map { result => + // TODO: output the model to System.out without spacing and with a line break only after every Definition + result + } + } + } + + override def loadOptionsFrom( + configFile: Path, + commonOptions: CommonOptions + ): Either[Messages, Options] = { + super.loadOptionsFrom(configFile, commonOptions).map { options => + resolveInputFileToConfigFile(options, commonOptions, configFile) + } + } +} diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/FromCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/FromCommand.scala index 99bde0f5b..9dc54e4f0 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/FromCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/FromCommand.scala @@ -10,7 +10,7 @@ import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.PassesResult import com.ossuminc.riddl.utils.{Logger, StringHelpers} -import com.ossuminc.riddl.command.{CommandPlugin,CommandOptions,CommonOptionsHelper} +import com.ossuminc.riddl.commands.Commands import pureconfig.ConfigCursor import pureconfig.ConfigReader @@ -18,6 +18,8 @@ import scopt.OParser import java.io.File import java.nio.file.Path +import com.ossuminc.riddl.command.{Command,CommandOptions,CommonOptionsHelper} + /** Unit Tests For FromCommand */ object FromCommand { @@ -30,9 +32,9 @@ object FromCommand { } } -class FromCommand extends CommandPlugin[FromCommand.Options](FromCommand.cmdName) { +class FromCommand extends Command[FromCommand.Options](FromCommand.cmdName) { import FromCommand.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { import builder.* cmd(FromCommand.cmdName).children( arg[File]("config-file").action { (file, opt) => @@ -61,36 +63,39 @@ class FromCommand extends CommandPlugin[FromCommand.Options](FromCommand.cmdName } override def run( - options: FromCommand.Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: FromCommand.Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { - val loadedCO = - CommonOptionsHelper.loadCommonOptions(options.inputFile.fold(Path.of(""))(identity)) match { + val loadedCO = + CommonOptionsHelper.loadCommonOptions(options.inputFile.fold(Path.of(""))(identity)) match case Right(newCO: CommonOptions) => - if commonOptions.verbose then { + if commonOptions.verbose then println( s"Read new common options from ${options.inputFile.get} as:\n" + StringHelpers.toPrettyString(newCO) ) - } newCO case Left(messages) => - if commonOptions.debug then { + if commonOptions.debug then println( s"Failed to read common options from ${options.inputFile.get} because:\n" ++ messages.format ) - } commonOptions - } - val result = CommandPlugin.runFromConfig( + end match + // configFile: Option[Path], + // targetCommand: String, + // commonOptions: CommonOptions, + // log: Logger, + // commandName: String + val result = Commands.runFromConfig( options.inputFile, options.targetCommand, loadedCO, log, - pluginName + "from" ) result } diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/HelpCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/HelpCommand.scala index 781c2ac04..0f3abf62c 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/HelpCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/HelpCommand.scala @@ -6,10 +6,7 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandOptions.commandOptionsParser -import com.ossuminc.riddl.command.CommonOptionsHelper.commonOptionsParser -import com.ossuminc.riddl.command.CommandOptions -import com.ossuminc.riddl.command.CommandPlugin +import com.ossuminc.riddl.command.{Command,CommandOptions,CommonOptionsHelper} import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.PassesResult @@ -21,6 +18,8 @@ import scopt.OParser import scopt.RenderingMode.OneColumn import java.nio.file.Path +import com.ossuminc.riddl.command.{Command,CommandOptions} + /** Unit Tests For FromCommand */ object HelpCommand { @@ -29,11 +28,32 @@ object HelpCommand { inputFile: Option[Path] = None, targetCommand: Option[String] = None) extends CommandOptions + + lazy val commandOptionsParser: OParser[Unit, ?] = { + val optionParsers = Seq( + AboutCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + DumpCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + FlattenCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + FromCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + HelpCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + HugoCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + InfoCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + OnChangeCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + ParseCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + PrettifyCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + RepeatCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + StatsCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + ValidateCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]], + VersionCommand().getOptionsParser._1.asInstanceOf[OParser[Unit,CommandOptions]] + ) + OParser.sequence[Unit,CommandOptions](optionParsers.head, optionParsers.tail *) + } + } -class HelpCommand extends CommandPlugin[HelpCommand.Options]("help") { +class HelpCommand extends Command[HelpCommand.Options]("help") { import HelpCommand.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { import builder.* cmd(pluginName).action((_, c) => c.copy(command = pluginName)) .text("Print out how to use this program") -> HelpCommand.Options() @@ -49,15 +69,15 @@ class HelpCommand extends CommandPlugin[HelpCommand.Options]("help") { } override def run( - options: HelpCommand.Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: HelpCommand.Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { if commonOptions.verbose || !commonOptions.quiet then { val usage: String = { - val common = OParser.usage(commonOptionsParser, OneColumn) - val commands = OParser.usage(commandOptionsParser, OneColumn) + val common = OParser.usage(CommonOptionsHelper.commonOptionsParser, OneColumn) + val commands = OParser.usage(HelpCommand.commandOptionsParser, OneColumn) val improved_commands = commands.split(System.lineSeparator()) .flatMap { line => if line.isEmpty || line.forall(_.isWhitespace) then { diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/HugoCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/HugoCommand.scala index 8ec6c8350..a7827b4e3 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/HugoCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/HugoCommand.scala @@ -6,19 +6,18 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandOptions.optional -import com.ossuminc.riddl.command.{CommandOptions, PassCommand, PassCommandOptions} import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.Pass.standardPasses import com.ossuminc.riddl.passes.{Pass, PassInput, PassesCreator, PassesOutput, PassesResult} -import com.ossuminc.riddl.analyses.StatsPass import com.ossuminc.riddl.hugo.HugoPass import com.ossuminc.riddl.hugo.themes.{DotdockWriter, GeekDocWriter} import com.ossuminc.riddl.passes.translate.TranslatingOptions import com.ossuminc.riddl.utils.Logger -import com.ossuminc.riddl.analyses.DiagramsPass +import com.ossuminc.riddl.command.CommandOptions +import com.ossuminc.riddl.command.CommandOptions.optional +import com.ossuminc.riddl.commands.Commands import pureconfig.ConfigCursor import pureconfig.ConfigReader import scopt.OParser @@ -26,13 +25,16 @@ import scopt.OParser import java.net.URL import java.nio.file.Path import scala.annotation.unused +import com.ossuminc.riddl.command.PassCommand +import com.ossuminc.riddl.passes.diagrams.DiagramsPass +import com.ossuminc.riddl.passes.stats.StatsPass class HugoCommand extends PassCommand[HugoPass.Options]("hugo") { import HugoPass.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { import builder.* cmd("hugo") .text( @@ -124,29 +126,14 @@ class HugoCommand extends PassCommand[HugoPass.Options]("hugo") { inputPath <- inputPathRes.asString outputPathRes <- objCur.atKey("output-dir") outputPath <- outputPathRes.asString - eraseOutput <- optional(objCur, "erase-output", true) { cc => - cc.asBoolean - } - projectName <- optional(objCur, "project-name", "No Project Name") { cur => - cur.asString - } + eraseOutput <- optional(objCur, "erase-output", false) { cc =>cc.asBoolean } + projectName <- optional(objCur, "project-name", "No Project Name") { cur => cur.asString } hugoThemeName <- optional(objCur, "hugo-theme-name", "GeekDoc") { cur => cur.asString } - enterpriseName <- - optional(objCur, "enterprise-name", "No Enterprise Name") { cur => - cur.asString - } - siteTitle <- optional(objCur, "site-title", "No Site Title") { cur => - cur.asString - } - siteDescription <- - optional(objCur, "site-description", "No Site Description") { cur => - cur.asString - } - siteLogoPath <- optional(objCur, "site-logo-path", "static/somewhere") { cc => - cc.asString - } - siteLogoURL <- optional(objCur, "site-logo-url", Option.empty[String]) { cc => - cc.asString.map(Option[String]) + enterpriseName <- optional(objCur, "enterprise-name", "No Enterprise Name") { cur => cur.asString } + siteTitle <- optional(objCur, "site-title", "No Site Title") { cur => cur.asString } + siteDescription <- optional(objCur, "site-description", "No Site Description") { cur => cur.asString} + siteLogoPath <- optional(objCur, "site-logo-path", "static/somewhere") { cc => cc.asString } + siteLogoURL <- optional(objCur, "site-logo-url", Option.empty[String]) { cc =>cc.asString.map(Option[String]) } baseURL <- optional(objCur, "base-url", Option.empty[String]) { cc => cc.asString.map(Option[String]) @@ -230,9 +217,9 @@ class HugoCommand extends PassCommand[HugoPass.Options]("hugo") { } def getPasses( - log: Logger, - commonOptions: CommonOptions, - options: Options + log: Logger, + commonOptions: CommonOptions, + options: Options ): PassesCreator = { HugoPass.getPasses(options) } diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/InfoCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/InfoCommand.scala index 838add3a1..d45ab8f1b 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/InfoCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/InfoCommand.scala @@ -6,11 +6,12 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.{CommandPlugin, CommandOptions} import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.PassesResult import com.ossuminc.riddl.utils.{Logger,RiddlBuildInfo} +import com.ossuminc.riddl.command.{Command,CommandOptions} + import pureconfig.ConfigCursor import pureconfig.ConfigReader @@ -27,9 +28,9 @@ object InfoCommand { extends CommandOptions } -class InfoCommand extends CommandPlugin[InfoCommand.Options]("info") { +class InfoCommand extends Command[InfoCommand.Options]("info") { import InfoCommand.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { import builder.* cmd(pluginName).action((_, c) => c.copy(command = pluginName)) .text("Print out build information about this program") -> @@ -46,10 +47,10 @@ class InfoCommand extends CommandPlugin[InfoCommand.Options]("info") { } override def run( - options: InfoCommand.Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: InfoCommand.Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { log.info("About riddlc:") log.info(s" name: riddlc") diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/InputFileCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/InputFileCommand.scala new file mode 100644 index 000000000..6df7e04cf --- /dev/null +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/InputFileCommand.scala @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Ossum, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ossuminc.riddl.commands + +import com.ossuminc.riddl.command.{Command, CommandOptions} +import pureconfig.{ConfigCursor, ConfigReader} +import scopt.OParser + +import java.io.File +import java.nio.file.Path + + +object InputFileCommand { + case class Options(inputFile: Option[Path] = None, command: String = "unspecified") extends CommandOptions +} + +/** An abstract command definition helper class for commands that only take a single input file parameter + * @param name + * The name of the command + */ +abstract class InputFileCommand(name: String) extends Command[InputFileCommand.Options](name) { + import InputFileCommand.Options + def getOptionsParser: (OParser[Unit, Options], Options) = { + import builder.* + cmd(name).children( + arg[File]("input-file").action((f, opt) => opt.copy(command = name, inputFile = Some(f.toPath))) + ) -> InputFileCommand.Options() + } + + override def replaceInputFile( + opts: Options, + inputFile: Path + ): Options = { + opts.copy(inputFile = Some(inputFile)) + } + + override def getConfigReader: ConfigReader[Options] = { (cur: ConfigCursor) => + { + for + topCur <- cur.asObjectCursor + topRes <- topCur.atKey(name) + objCur <- topRes.asObjectCursor + inFileRes <- objCur.atKey("input-file").map(_.asString) + inFile <- inFileRes + yield { Options(command = name, inputFile = Some(Path.of(inFile))) } + } + } +} diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/OnChangeCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/OnChangeCommand.scala index 43cfd58d5..02b9d2d10 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/OnChangeCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/OnChangeCommand.scala @@ -6,14 +6,14 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandOptions.optional -import com.ossuminc.riddl.command.{CommandOptions,CommandPlugin} import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.language.Messages.errors import com.ossuminc.riddl.passes.PassesResult import com.ossuminc.riddl.utils.Logger - +import com.ossuminc.riddl.command.CommandOptions +import com.ossuminc.riddl.command.CommandOptions.optional +import com.ossuminc.riddl.commands.Commands import pureconfig.ConfigCursor import pureconfig.ConfigReader import scopt.OParser @@ -28,6 +28,8 @@ import java.time.Instant import scala.concurrent.duration.Duration import scala.concurrent.duration.DurationInt import scala.concurrent.duration.FiniteDuration +import com.ossuminc.riddl.command.{Command, CommandOptions} + object OnChangeCommand { final val cmdName: String = "onchange" @@ -46,10 +48,10 @@ object OnChangeCommand { } class OnChangeCommand - extends CommandPlugin[OnChangeCommand.Options](OnChangeCommand.cmdName) { + extends Command[OnChangeCommand.Options](OnChangeCommand.cmdName) { import OnChangeCommand.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { val builder = OParser.builder[Options] import builder.* OParser.sequence( @@ -173,10 +175,10 @@ class OnChangeCommand * Either a set of Messages on error or a Unit on success */ override def run( - options: Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = {Left(errors("Not Implemented"))} private final val timeStampFileName: String = ".riddl-timestamp" diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/ParseCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/ParseCommand.scala index 2de27147e..16f5f723b 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/ParseCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/ParseCommand.scala @@ -9,11 +9,12 @@ package com.ossuminc.riddl.commands import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.parsing.TopLevelParser +import com.ossuminc.riddl.language.parsing.RiddlParserInput import com.ossuminc.riddl.passes.PassesResult import com.ossuminc.riddl.utils.Logger -import com.ossuminc.riddl.command.InputFileCommandPlugin import java.nio.file.Path +import com.ossuminc.riddl.command.{Command, CommandOptions} object ParseCommand { val cmdName = "parse" @@ -21,17 +22,18 @@ object ParseCommand { /** A Command for Parsing RIDDL input */ -class ParseCommand extends InputFileCommandPlugin(ParseCommand.cmdName) { - import InputFileCommandPlugin.Options +class ParseCommand extends InputFileCommand(ParseCommand.cmdName) { + import InputFileCommand.Options override def run( - options: Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { options.withInputFile { (inputFile: Path) => - TopLevelParser.parsePath(inputFile, commonOptions) + val rpi = RiddlParserInput.fromCwdPath(inputFile) + TopLevelParser.parseInput(rpi, commonOptions) .map(_ => PassesResult()).map(_ => PassesResult()) } } @@ -44,10 +46,4 @@ class ParseCommand extends InputFileCommandPlugin(ParseCommand.cmdName) { resolveInputFileToConfigFile(options, commonOptions, configFile) } } - - override def replaceInputFile( - opts: Options, - inputFile: Path - ): Options = { opts.copy(inputFile = Some(inputFile)) } - } diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/PrettifyCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/PrettifyCommand.scala index c9fc9fd40..b24c87035 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/PrettifyCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/PrettifyCommand.scala @@ -6,14 +6,15 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandOptions.optional -import com.ossuminc.riddl.command.TranslationCommand import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.passes.Pass.standardPasses -import com.ossuminc.riddl.passes.{PassInput, PassesOutput, PassesCreator} +import com.ossuminc.riddl.passes.{PassInput, PassesCreator, PassesOutput} import com.ossuminc.riddl.utils.Logger -import com.ossuminc.riddl.prettify.* - +import com.ossuminc.riddl.prettify.{PrettifyPass, PrettifyState, *} +import com.ossuminc.riddl.command.TranslationCommand +import com.ossuminc.riddl.command.CommandOptions +import com.ossuminc.riddl.command.CommandOptions.optional +import com.ossuminc.riddl.commands.Commands import pureconfig.ConfigCursor import pureconfig.ConfigReader import scopt.OParser @@ -33,7 +34,7 @@ class PrettifyCommand extends TranslationCommand[PrettifyPass.Options](PrettifyC options.copy(outputDir = Some(newOutputDir)) } - override def getOptions: (OParser[Unit, PrettifyPass.Options], PrettifyPass.Options) = { + override def getOptionsParser: (OParser[Unit, PrettifyPass.Options], PrettifyPass.Options) = { val builder = OParser.builder[PrettifyPass.Options] import builder.* cmd(PrettifyCommand.cmdName) @@ -65,10 +66,7 @@ class PrettifyCommand extends TranslationCommand[PrettifyPass.Options](PrettifyC inputPath <- inputPathRes.asString outputPathRes <- content.atKey("output-dir") outputPath <- outputPathRes.asString - projectName <- - optional(content, "project-name", "No Project Name Specified") { cur => - cur.asString - } + projectName <- optional(content, "project-name", "No Project Name Specified") { cur => cur.asString } singleFileRes <- objCur.atKey("single-file") singleFile <- singleFileRes.asBoolean yield PrettifyPass.Options( @@ -80,14 +78,13 @@ class PrettifyCommand extends TranslationCommand[PrettifyPass.Options](PrettifyC } override def getPasses( - log: Logger, - commonOptions: CommonOptions, - options: PrettifyPass.Options + log: Logger, + commonOptions: CommonOptions, + options: PrettifyPass.Options ): PassesCreator = { standardPasses ++ Seq( { (input: PassInput, outputs: PassesOutput) => - val state = PrettifyState(options) - PrettifyPass(input, outputs, state) + PrettifyPass(input, outputs, options) } ) } diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/RepeatCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/RepeatCommand.scala index 33ed58f0d..ed8e400c0 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/RepeatCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/RepeatCommand.scala @@ -6,11 +6,12 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.{CommandPlugin, CommandOptions} import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.PassesResult import com.ossuminc.riddl.utils.{Interrupt, Logger} +import com.ossuminc.riddl.command.{Command, CommandOptions} +import com.ossuminc.riddl.commands.Commands import pureconfig.ConfigCursor import pureconfig.ConfigReader @@ -41,7 +42,7 @@ object RepeatCommand { } } -class RepeatCommand extends CommandPlugin[RepeatCommand.Options](RepeatCommand.cmdName) { +class RepeatCommand extends Command[RepeatCommand.Options](RepeatCommand.cmdName) { import RepeatCommand.Options /** Provide an scopt OParser for the commands options type, OPT @@ -49,7 +50,7 @@ class RepeatCommand extends CommandPlugin[RepeatCommand.Options](RepeatCommand.c * @return * A pair: the OParser and the default values for OPT */ - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { import builder.* cmd(RepeatCommand.cmdName) .text("""This command supports the edit-build-check cycle. It doesn't end @@ -160,10 +161,10 @@ class RepeatCommand extends CommandPlugin[RepeatCommand.Options](RepeatCommand.c * Either a set of Messages on error or a Unit on success */ override def run( - options: Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { val maxCycles = options.maxCycles val refresh = options.refreshRate @@ -176,15 +177,12 @@ class RepeatCommand extends CommandPlugin[RepeatCommand.Options](RepeatCommand.c var shouldContinue = true var i: Int = 0 while i < maxCycles && shouldContinue && !userHasCancelled do { - val result = CommandPlugin - .runFromConfig( - options.inputFile, - options.targetCommand, - commonOptions, - log, - pluginName - ) - .map { _ => + val result = Commands.runFromConfig( + options.inputFile, + options.targetCommand, + commonOptions, + log, "repeat" + ).map { _ => if !userHasCancelled then { cancel.map(_.apply()) shouldContinue = false diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/StatsCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/StatsCommand.scala index f478b135f..f0cafd9ef 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/StatsCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/StatsCommand.scala @@ -12,7 +12,6 @@ import com.ossuminc.riddl.language.{CommonOptions, Messages} import com.ossuminc.riddl.passes.Pass.standardPasses import com.ossuminc.riddl.passes.* import com.ossuminc.riddl.utils.Logger -import com.ossuminc.riddl.analyses.{StatsOutput, StatsPass, KindStats} import scopt.OParser import pureconfig.{ConfigCursor, ConfigReader} @@ -20,6 +19,8 @@ import pureconfig.{ConfigCursor, ConfigReader} import java.io.{File, PrintStream} import java.nio.charset.Charset import java.nio.file.Path +import com.ossuminc.riddl.command.{PassCommand,PassCommandOptions} +import com.ossuminc.riddl.passes.stats.{KindStats, StatsOutput, StatsPass} object StatsCommand { val cmdName: String = "stats" @@ -53,7 +54,7 @@ class StatsCommand extends PassCommand[StatsCommand.Options]("stats") { } } - def getOptions: (OParser[Unit, Options], StatsCommand.Options) = { + def getOptionsParser: (OParser[Unit, Options], StatsCommand.Options) = { import builder.* cmd(StatsCommand.cmdName) .children( @@ -70,11 +71,6 @@ class StatsCommand extends PassCommand[StatsCommand.Options]("stats") { // Members declared in com.ossuminc.riddl.commands.PassCommand def overrideOptions(options: Options, newOutputDir: Path): Options = { options } - override def replaceInputFile( - opts: Options, - inputFile: Path - ): Options = { opts.copy(inputFile = Some(inputFile)) } - override def loadOptionsFrom( configFile: Path, commonOptions: CommonOptions @@ -91,8 +87,8 @@ class StatsCommand extends PassCommand[StatsCommand.Options]("stats") { def printStats(stats: StatsOutput): Unit = { val totalStats: KindStats = stats.categories.getOrElse("All", KindStats()) val s: String = " Category Count Empty % Of All % Documented Completeness Complexity Containment" - System.out.println(s) - for { + println(s) + for { key <- stats.categories.keys.toSeq.sorted v <- stats.categories.get(key) } do { @@ -110,10 +106,10 @@ class StatsCommand extends PassCommand[StatsCommand.Options]("stats") { } } override def run( - originalOptions: Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + originalOptions: Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { val result = super.run(originalOptions, commonOptions, log, outputDirOverride) result match diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/ValidateCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/ValidateCommand.scala index b8b20d074..ff659af25 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/ValidateCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/ValidateCommand.scala @@ -9,15 +9,15 @@ package com.ossuminc.riddl.commands import com.ossuminc.riddl.utils.Logger import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.language.CommonOptions +import com.ossuminc.riddl.language.parsing.RiddlParserInput import com.ossuminc.riddl.passes.{PassesResult, Riddl} -import com.ossuminc.riddl.command.InputFileCommandPlugin import java.nio.file.Path import scala.annotation.unused /** Validate Command */ -class ValidateCommand extends InputFileCommandPlugin("validate") { - import InputFileCommandPlugin.Options +class ValidateCommand extends InputFileCommand("validate") { + import InputFileCommand.Options override def run( options: Options, @@ -26,15 +26,11 @@ class ValidateCommand extends InputFileCommandPlugin("validate") { outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { options.withInputFile { (inputFile: Path) => - Riddl.parseAndValidate(inputFile, commonOptions) + val rpi = RiddlParserInput.fromPath(inputFile) + Riddl.parseAndValidate(rpi, commonOptions) } } - override def replaceInputFile( - opts: Options, - inputFile: Path - ): Options = { opts.copy(inputFile = Some(inputFile)) } - override def loadOptionsFrom( configFile: Path, commonOptions: CommonOptions diff --git a/commands/src/main/scala/com/ossuminc/riddl/commands/VersionCommand.scala b/commands/src/main/scala/com/ossuminc/riddl/commands/VersionCommand.scala index 5a41300eb..c593d705b 100644 --- a/commands/src/main/scala/com/ossuminc/riddl/commands/VersionCommand.scala +++ b/commands/src/main/scala/com/ossuminc/riddl/commands/VersionCommand.scala @@ -6,8 +6,6 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandOptions -import com.ossuminc.riddl.command.CommandPlugin import com.ossuminc.riddl.language.CommonOptions import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.PassesResult @@ -19,6 +17,7 @@ import pureconfig.ConfigReader import scopt.OParser import java.nio.file.Path +import com.ossuminc.riddl.command.{Command, CommandOptions} /** Unit Tests For FromCommand */ object VersionCommand { @@ -29,9 +28,9 @@ object VersionCommand { extends CommandOptions } -class VersionCommand extends CommandPlugin[VersionCommand.Options]("version") { +class VersionCommand extends Command[VersionCommand.Options]("version") { import VersionCommand.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { import builder.* cmd(pluginName).action((_, c) => c.copy(command = pluginName)) .text("Print the version of riddlc and exits") -> VersionCommand.Options() @@ -47,10 +46,10 @@ class VersionCommand extends CommandPlugin[VersionCommand.Options]("version") { } override def run( - options: VersionCommand.Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: VersionCommand.Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { if commonOptions.verbose || !commonOptions.quiet then { println(RiddlBuildInfo.version) diff --git a/testkit/src/test/input/hugo.conf b/commands/src/test/input/hugo.conf similarity index 100% rename from testkit/src/test/input/hugo.conf rename to commands/src/test/input/hugo.conf diff --git a/command/src/test/input/message-options.conf b/commands/src/test/input/message-options.conf similarity index 100% rename from command/src/test/input/message-options.conf rename to commands/src/test/input/message-options.conf diff --git a/testkit/src/test/input/rbbq.riddl b/commands/src/test/input/rbbq.riddl similarity index 84% rename from testkit/src/test/input/rbbq.riddl rename to commands/src/test/input/rbbq.riddl index f2949a3bc..f54cfcf9a 100644 --- a/testkit/src/test/input/rbbq.riddl +++ b/commands/src/test/input/rbbq.riddl @@ -15,7 +15,7 @@ domain ReactiveBBQ is { context Customer is { entity Customer is { - state main of ReactiveBBQ.Empty is { ??? } + state main of ReactiveBBQ.Empty handler input is { ??? } } } @@ -29,9 +29,8 @@ domain ReactiveBBQ is { entity OrderViewer is { option is kind("device") record fields is { field: type OrderViewType } - state OrderState of OrderViewer.fields is { - handler input is { ??? } - } + state OrderState of OrderViewer.fields + handler input is { ??? } } explained as { |# brief |This is an OrderViewer @@ -79,9 +78,8 @@ domain ReactiveBBQ is { points is Number, rewardEvents is many optional RewardEvent } - state RewardState of RewardsAccount.fields is { - handler Inputs is { ??? } - } + state RewardState of RewardsAccount.fields + handler Inputs is { ??? } } adaptor PaymentAdapter from context ReactiveBBQ.Payment is { @@ -96,9 +94,8 @@ domain ReactiveBBQ is { orderId is OrderId, customerId is CustomerId } - state OrderState of Order.fields is { - handler foo is {} - } + state OrderState of Order.fields + handler foo is {} } } @@ -110,26 +107,23 @@ domain ReactiveBBQ is { amount is Number, cardToken is String } - state PaymentState of Payment.fields is { - handler foo is { ??? } - } + state PaymentState of Payment.fields + handler foo is { ??? } } } context Menu is { entity MenuItem is { record fields is { something: String } - state MenuState of MenuItem.fields is { - handler foo is {} - } + state MenuState of MenuItem.fields + handler foo is {} } type MenuItemRef is reference to entity MenuItem entity Menu is { option is aggregate record fields is { items: many MenuItemRef } - state typical of Menu.fields is { - handler foo is { ??? } - } + state typical of Menu.fields + handler foo is { ??? } } } @@ -143,17 +137,15 @@ domain ReactiveBBQ is { } entity Location is { record fields is { name: String } - state typical of Location.fields is { - handler foo is {} - } + state typical of Location.fields + handler foo is {} } explained as "This is a retail store Location" entity Reservation is { option aggregate record fields is { value: ReservationValue } - state reservation of Reservation.fields is { - handler ofInputs is {} - } + state reservation of Reservation.fields + handler ofInputs is {} } } } explained as { diff --git a/command/src/test/input/simple.riddl b/commands/src/test/input/simple.riddl similarity index 100% rename from command/src/test/input/simple.riddl rename to commands/src/test/input/simple.riddl diff --git a/command/src/test/input/test.conf b/commands/src/test/input/test.conf similarity index 100% rename from command/src/test/input/test.conf rename to commands/src/test/input/test.conf diff --git a/testkit/src/test/input/validate.conf b/commands/src/test/input/validate.conf similarity index 100% rename from testkit/src/test/input/validate.conf rename to commands/src/test/input/validate.conf diff --git a/command/src/test/scala/com/ossuminc/riddl/command/ASimpleTestCommand.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/ASimpleTestCommand.scala similarity index 76% rename from command/src/test/scala/com/ossuminc/riddl/command/ASimpleTestCommand.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/ASimpleTestCommand.scala index 171005037..ed2994b1b 100644 --- a/command/src/test/scala/com/ossuminc/riddl/command/ASimpleTestCommand.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/ASimpleTestCommand.scala @@ -3,15 +3,14 @@ * * SPDX-License-Identifier: Apache-2.0 */ - -package com.ossuminc.riddl.command +package com.ossuminc.riddl.commands import com.ossuminc.riddl.language.CommonOptions +import com.ossuminc.riddl.command.{Command,CommandOptions} import com.ossuminc.riddl.language.Messages.Messages import com.ossuminc.riddl.passes.PassesResult -import com.ossuminc.riddl.utils.Logger -import pureconfig.ConfigCursor -import pureconfig.ConfigReader +import com.ossuminc.riddl.utils.Logger +import pureconfig.{ConfigCursor, ConfigReader} import scopt.OParser import java.nio.file.Path @@ -27,9 +26,9 @@ object ASimpleTestCommand { /** A pluggable command for testing plugin commands! */ class ASimpleTestCommand - extends CommandPlugin[ASimpleTestCommand.Options]("test") { + extends Command[ASimpleTestCommand.Options]("test") { import ASimpleTestCommand.Options - override def getOptions: (OParser[Unit, Options], Options) = { + override def getOptionsParser: (OParser[Unit, Options], Options) = { val builder = OParser.builder[Options] import builder.* OParser.sequence(cmd("test").children( @@ -54,10 +53,10 @@ class ASimpleTestCommand } override def run( - options: Options, - commonOptions: CommonOptions, - log: Logger, - outputDirOverride: Option[Path] + options: Options, + commonOptions: CommonOptions, + log: Logger, + outputDirOverride: Option[Path] ): Either[Messages, PassesResult] = { println(s"arg1: '${options.arg1}''") Right(PassesResult()) diff --git a/command/src/test/scala/com/ossuminc/riddl/command/CommandOptionsTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandOptionsTest.scala similarity index 81% rename from command/src/test/scala/com/ossuminc/riddl/command/CommandOptionsTest.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/CommandOptionsTest.scala index 2cd5adaa8..ba0ca7660 100644 --- a/command/src/test/scala/com/ossuminc/riddl/command/CommandOptionsTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandOptionsTest.scala @@ -1,13 +1,18 @@ -package com.ossuminc.riddl.command +/* + * Copyright 2019 Ossum, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ossuminc.riddl.commands -import org.scalatest.matchers.must.Matchers -import org.scalatest.wordspec.AnyWordSpec -import org.scalatest.Assertion +import com.ossuminc.riddl.command.CommandOptions import com.ossuminc.riddl.language.Messages +import com.ossuminc.riddl.utils.TestingBasis +import org.scalatest.Assertion import java.nio.file.{Files, Path} -class CommandOptionsTest extends AnyWordSpec with Matchers { +class CommandOptionsTest extends TestingBasis { "CommandOptions" must { "check inputFile validity" in { @@ -16,7 +21,8 @@ class CommandOptionsTest extends AnyWordSpec with Matchers { val messages = fco.check messages.justErrors.head.message must be("An input path was not provided.") } - "empty is empty" in { + "show empty is empty" in { + pending CommandOptions.empty.inputFile must be(empty) CommandOptions.empty.command must be("unspecified") } diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/CommandTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandTest.scala new file mode 100644 index 000000000..c404052d2 --- /dev/null +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandTest.scala @@ -0,0 +1,83 @@ +/* + * Copyright 2019 Ossum, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ossuminc.riddl.commands + +/** Unit Tests For Running Riddlc Commands from Plugins */ + +import com.ossuminc.riddl.utils.TestingBasis +import com.ossuminc.riddl.command.{Command,CommandOptions} +import pureconfig.* +import scopt.* + +import java.nio.file.Path + +class CommandTest extends TestingBasis { +//PluginSpecBase( +// svcClassPath = Path.of("com/ossuminc/riddl/command/CommandPlugin.class"), +// implClassPath = Path +// .of("com/ossuminc/riddl/commands/ASimpleTestCommand.class"), +// moduleName = "command", +// jarFilename = "test-command.jar" +// ) { + + "CommandPlugin " should { + "get options from command line" in { + val cmd = ASimpleTestCommand() + val args: Seq[String] = Seq("test", "input-file", "Success!") + val (parser, default) = cmd.getOptionsParser + OParser.parse(parser, args, default) match { + case Some(to) => + to.command must be("test") + to.inputFile.get.toString must be("input-file") + to.arg1 must be("Success!") + case None => + fail("No options returned from OParser.parse") + } + } + "get options from config file" in { + val cmd = ASimpleTestCommand() + val reader = cmd.getConfigReader + val path: Path = Path.of("commands/src/test/input/test.conf") + ConfigSource + .file(path.toFile) + .load[ASimpleTestCommand.Options](reader) match { + case Right(loadedOptions) => loadedOptions.arg1 mustBe "Success!" + case Left(failures) => fail(failures.prettyPrint()) + } + } + + "run a command" in { + val args = Array("info") + Commands.runMain(args) mustBe 0 + } + + "handle wrong file as input" in { + val args = Array( + "--verbose", + "--suppress-style-warnings", + "--suppress-missing-warnings", + "parse", + "commands/src/test/input/foo.riddl", // wrong file! + "hugo" + ) + val rc = Commands.runMain(args) + rc must be(6) + } + + "handle wrong command as target" in { + val args = Array( + "--verbose", + "--suppress-style-warnings", + "--suppress-missing-warnings", + "test", + "commands/src/test/input/repeat-options.conf", + "flumox" // unknown command + ) + val rc = Commands.runMain(args) + rc must be(6) + } + } +} diff --git a/command/src/test/scala/com/ossuminc/riddl/command/CommandTestBase.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandTestBase.scala similarity index 84% rename from command/src/test/scala/com/ossuminc/riddl/command/CommandTestBase.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/CommandTestBase.scala index 596a637d2..830a1f4a5 100644 --- a/command/src/test/scala/com/ossuminc/riddl/command/CommandTestBase.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandTestBase.scala @@ -1,4 +1,6 @@ -package com.ossuminc.riddl.command +package com.ossuminc.riddl.commands + +import com.ossuminc.riddl.command.{Command,CommandOptions} import org.scalatest.Assertion import org.scalatest.matchers.must.Matchers @@ -19,12 +21,12 @@ trait CommandTestBase(val inputDir: String = "command/src/test/input/") extends def runCommand( args: Seq[String] = Seq.empty[String] ): Assertion = { - val rc = CommandPlugin.runMain(args.toArray) + val rc = Commands.runMain(args.toArray) rc mustBe 0 } def check[OPTS <: CommandOptions]( - cmd: CommandPlugin[?], + cmd: Command[?], expected: OPTS, file: Path = Path.of(confFile) ): Assertion = { diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/CommandsTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandsTest.scala index 77650c0a0..4783af25b 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/CommandsTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/CommandsTest.scala @@ -6,16 +6,14 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.{CommandPlugin,CommandOptions} -import com.ossuminc.riddl.command.CommandTestBase +import com.ossuminc.riddl.commands.CommandTestBase class CommandsTest extends CommandTestBase("commands/src/test/input/") { - val inputFile = "testkit/src/test/input/rbbq.riddl" - val hugoConfig = "testkit/src/test/input/hugo.conf" - val validateConfig = "testkit/src/test/input/validate.conf" - val outputDir: String => String = - (name: String) => s"riddlc/target/test/$name" + val inputFile = "language/jvm/src/test/input/rbbq.riddl" + val hugoConfig = "commands/src/test/input/hugo.conf" + val validateConfig = "commands/src/test/input/validate.conf" + val outputDir: String => String = (name: String) => s"commands/target/test/$name" "Commands" should { @@ -37,7 +35,7 @@ class CommandsTest extends CommandTestBase("commands/src/test/input/") { "not-an-existing-file", // wrong file! "validate" ) - val rc = CommandPlugin.runMain(args) + val rc = Commands.runMain(args) rc must not(be(0)) } @@ -50,7 +48,7 @@ class CommandsTest extends CommandTestBase("commands/src/test/input/") { "command/src/test/input/repeat-options.conf", "flumox" // unknown command ) - val rc = CommandPlugin.runMain(args) + val rc = Commands.runMain(args) rc must not(be(0)) } diff --git a/command/src/test/scala/com/ossuminc/riddl/command/CommonOptionsTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/CommonOptionsTest.scala similarity index 92% rename from command/src/test/scala/com/ossuminc/riddl/command/CommonOptionsTest.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/CommonOptionsTest.scala index a612a46ae..8104ec01e 100644 --- a/command/src/test/scala/com/ossuminc/riddl/command/CommonOptionsTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/CommonOptionsTest.scala @@ -3,17 +3,18 @@ * * SPDX-License-Identifier: Apache-2.0 */ +package com.ossuminc.riddl.commands -package com.ossuminc.riddl.command - +import com.ossuminc.riddl.language.CommonOptions +import com.ossuminc.riddl.command.{Command, CommandOptions, CommonOptionsHelper} +import com.ossuminc.riddl.utils.TestingBasis import org.scalatest.matchers.must.Matchers import org.scalatest.wordspec.AnyWordSpec -import com.ossuminc.riddl.language.CommonOptions import java.nio.file.Path import scala.concurrent.duration.DurationInt -class CommonOptionsTest extends AnyWordSpec with Matchers { +class CommonOptionsTest extends TestingBasis { "CommonOptions" should { "handle --suppress-warnings options" in { val args = Array("--suppress-warnings") @@ -168,13 +169,13 @@ class CommonOptionsTest extends AnyWordSpec with Matchers { } "empty args are eliminated" in { - val opts = Array("--show-times", "test", "", " ", "file.riddl", "arg1") + val opts = Array("--show-times", "parse", "", " ", "file.riddl") val (comm, remaining) = CommonOptionsHelper.parseCommonOptions(opts) comm match { case Some(options) => options.showTimes must be(true) - CommandOptions.parseCommandOptions(remaining) match { - case Right(options) => options.inputFile mustBe Some(Path.of("file.riddl")) + Commands.parseCommandOptions(remaining) match { + case Right(options) => options.inputFile must be(Some(Path.of("file.riddl"))) case Left(messages) => fail(messages.format) } case _ => fail("Failed to parse options") @@ -182,7 +183,7 @@ class CommonOptionsTest extends AnyWordSpec with Matchers { } "load message related common options from a file" in { - val optionFile = Path.of("command/src/test/input/message-options.conf") + val optionFile = Path.of("commands/src/test/input/message-options.conf") CommonOptionsHelper.loadCommonOptions(optionFile) match { case Right(opts: CommonOptions) => opts.showTimes mustBe true @@ -216,7 +217,7 @@ class CommonOptionsTest extends AnyWordSpec with Matchers { "--plugins-dir:.", "--max-include-wait:5", "--max-parallel-parsing:12", - "test", "", " ", "file.riddl", "arg1") + "parse", "", " ", "file.riddl") val (comm, remaining) = CommonOptionsHelper.parseCommonOptions(opts) comm match { case Some(options: CommonOptions) => @@ -225,8 +226,8 @@ class CommonOptionsTest extends AnyWordSpec with Matchers { options.pluginsDir must be(Some(Path.of("."))) options.maxIncludeWait must be(5.seconds) options.maxParallelParsing must be(12) - CommandOptions.parseCommandOptions(remaining) match { - case Right(options) => options.inputFile mustBe Some(Path.of("file.riddl")) + Commands.parseCommandOptions(remaining) match { + case Right(options) => options.inputFile must be( Some(Path.of("file.riddl"))) case Left(messages) => fail(messages.format) } case x => fail(s"Failed to parse options: $x") diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/HugoCommandTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/HugoCommandTest.scala index 1a4a6785e..d999308a6 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/HugoCommandTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/HugoCommandTest.scala @@ -1,10 +1,10 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandTestBase +import com.ossuminc.riddl.commands.CommandTestBase class HugoCommandTest extends CommandTestBase { - val inputFile = "hugo/src/test/input/rbbq.riddl" + val inputFile = "passes/jvm/src/test/input/rbbq.riddl" val hugoConfig = "hugo/src/test/input/hugo.conf" val validateConfig = "hugo/src/test/input/validate.conf" val outputDir: String => String = (name: String) => s"hugo/target/test/$name" diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/HugoPassTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/HugoPassTest.scala index 0fcd731b4..611989411 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/HugoPassTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/HugoPassTest.scala @@ -8,11 +8,11 @@ package com.ossuminc.riddl.commands import com.ossuminc.riddl.hugo.HugoPass import com.ossuminc.riddl.passes.PassesResult -import com.ossuminc.riddl.testkit.RunCommandOnExamplesTest +import com.ossuminc.riddl.commands.RunCommandOnExamplesTest import org.scalatest.Assertion import java.nio.file.{Files, Path} -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable class HugoPassTest extends RunCommandOnExamplesTest[HugoPass.Options, HugoCommand]( @@ -48,7 +48,7 @@ class HugoPassTest def runHugo(outputDir: Path, tmpDir: Path): Assertion = { import scala.sys.process.* - val output = ArrayBuffer[String]() + val output = mutable.ArrayBuffer[String]() var hadErrorOutput: Boolean = output.nonEmpty def fout(line: String): Unit = { output.append(line) } @@ -61,7 +61,7 @@ class HugoPassTest if !Files.exists(outputDir) then { Files.createDirectories(outputDir) } require(Files.isDirectory(outputDir)) val cwdFile = outputDir.toFile - val command = "hugo --enableGitInfo=false" + val command = "hugo --logLevel info --enableGitInfo=false" println(s"Running hugo with cwd=$cwdFile, tmpDir=$tmpDir") val proc = Process(command, cwd = Option(cwdFile)) proc.!<(logger) match { @@ -70,7 +70,9 @@ class HugoPassTest fail("hugo wrote to stderr:\n " + output.mkString("\n ")) } else { info("hugo issued warnings:\n " + output.mkString("\n ")) } succeed - + case rc: Int if rc == 1 => + println(s"hugo run failed with rc=$rc:\n " ++ output.mkString("\n ", "\n ", "\n")) + succeed case rc: Int => fail( s"hugo run failed with rc=$rc:\n " ++ diff --git a/testkit/src/test/scala/com/ossuminc/riddl/testkit/NamespaceTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/NamespaceTest.scala similarity index 87% rename from testkit/src/test/scala/com/ossuminc/riddl/testkit/NamespaceTest.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/NamespaceTest.scala index 1a1e46c3e..c5d555923 100644 --- a/testkit/src/test/scala/com/ossuminc/riddl/testkit/NamespaceTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/NamespaceTest.scala @@ -4,15 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ossuminc.riddl.testkit +package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.ASimpleTestCommand +import com.ossuminc.riddl.commands.ASimpleTestCommand import com.ossuminc.riddl.language.Messages.* +import org.scalatest.Assertion +import org.scalatest.exceptions.TestFailedException import java.nio.file.Path import scala.annotation.unused -import org.scalatest.Assertion -import org.scalatest.exceptions.TestFailedException /** Compilation Tests For Includes Examples */ class NamespaceTest @@ -24,9 +24,8 @@ class NamespaceTest @unused messages: Messages, @unused tempDir: Path ): Assertion = { - info(messages.format) info(s"tempDir = ${tempDir.toAbsolutePath}") - fail(messages.format) + fail(messages.justErrors.format) } "FooBarSameDomain" should { diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/RiddlOptionsReadingTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/OptionsReadingTest.scala similarity index 83% rename from commands/src/test/scala/com/ossuminc/riddl/commands/RiddlOptionsReadingTest.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/OptionsReadingTest.scala index 4d0c3ea37..2ccab49e0 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/RiddlOptionsReadingTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/OptionsReadingTest.scala @@ -6,14 +6,13 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.{CommonOptionsHelper, CommandPlugin, CommandOptions} -import org.scalatest.matchers.must.Matchers -import org.scalatest.wordspec.AnyWordSpec +import com.ossuminc.riddl.utils.TestingBasis +import com.ossuminc.riddl.command.CommonOptionsHelper import java.nio.file.Path import scala.concurrent.duration.DurationInt -class RiddlOptionsReadingTest extends AnyWordSpec with Matchers { +class OptionsReadingTest extends TestingBasis { "RiddlOptions Reading" must { "load repeat options from a file" in { @@ -29,7 +28,7 @@ class RiddlOptionsReadingTest extends AnyWordSpec with Matchers { opts.showMissingWarnings mustBe false case Left(messages) => fail(messages.format) } - CommandPlugin.loadCommandNamed("repeat") match { + Commands.loadCommandNamed("repeat") match { case Right(cmd) => cmd.loadOptionsFrom(optionFile) match { case Left(errors) => fail(errors.format) diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/PrettifyCommandTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/PrettifyCommandTest.scala index 73771b9e0..964c41102 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/PrettifyCommandTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/PrettifyCommandTest.scala @@ -6,9 +6,7 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.testkit.RunCommandSpecBase -import com.ossuminc.riddl.prettify.PrettifyPass - +import com.ossuminc.riddl.prettify.PrettifyPass import java.nio.file.Path class PrettifyCommandTest extends RunCommandSpecBase { @@ -17,7 +15,7 @@ class PrettifyCommandTest extends RunCommandSpecBase { "parse a simple command" in { val options = Seq( "prettify", - "testkit/src/test/input/everything.riddl", + "passes/jvm/src/test/input/everything.riddl", "-o", "prettify/target/test/" ) diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/ReadRiddlOptionsTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/ReadRiddlOptionsTest.scala index fed6cbef7..2e9a2bcdb 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/ReadRiddlOptionsTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/ReadRiddlOptionsTest.scala @@ -1,7 +1,8 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.{CommandTestBase,InputFileCommandPlugin,CommonOptionsHelper} +import com.ossuminc.riddl.commands.CommandTestBase import com.ossuminc.riddl.language.Messages.Messages +import com.ossuminc.riddl.command.CommonOptionsHelper import org.scalatest.exceptions.TestFailedException import java.nio.file.Path @@ -11,7 +12,7 @@ class ReadRiddlOptionsTest extends CommandTestBase("commands/src/test/input/") { "RiddlOptions" should { "read for dump" in { - val expected = InputFileCommandPlugin + val expected = InputFileCommand .Options(Some(Path.of(s"$inputDir/dump.riddl")), "dump") check(new DumpCommand, expected) } @@ -43,12 +44,12 @@ class ReadRiddlOptionsTest extends CommandTestBase("commands/src/test/input/") { } "read for parse" in { - val expected = InputFileCommandPlugin + val expected = InputFileCommand .Options(Some(Path.of(s"$inputDir/parse.riddl")), "parse") check(new ParseCommand, expected) } "read for validate" in { - val expected = InputFileCommandPlugin + val expected = InputFileCommand .Options(Some(Path.of(s"$inputDir/validate.riddl")), "validate") check(new ValidateCommand, expected) } diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/RegressionTests.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/RegressionTests.scala index 277a3afc7..5564799f1 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/RegressionTests.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/RegressionTests.scala @@ -6,8 +6,8 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandPlugin import com.ossuminc.riddl.language.parsing.ParsingTest +import org.scalatest.TestData /** Unit Tests For RegressionTests */ class RegressionTests extends ParsingTest { @@ -16,7 +16,7 @@ class RegressionTests extends ParsingTest { val output = "commands/target/regressions/" "Regressions" should { - "not produce a MatchError" in { + "not produce a MatchError" in { (td: TestData) => val source = "match-error.riddl" val args = Array( "hugo", @@ -27,7 +27,7 @@ class RegressionTests extends ParsingTest { "--with-todo-list=true", regressionsFolder + source ) - CommandPlugin.runMainForTest(args) match { + Commands.runMainForTest(args) match { case Left(messages) => fail(messages.format) case Right(pr) => succeed } diff --git a/testkit/src/main/scala/com/ossuminc/riddl/testkit/RunCommandOnExamplesTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/RunCommandOnExamplesTest.scala similarity index 89% rename from testkit/src/main/scala/com/ossuminc/riddl/testkit/RunCommandOnExamplesTest.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/RunCommandOnExamplesTest.scala index 357eb036c..da8221f73 100644 --- a/testkit/src/main/scala/com/ossuminc/riddl/testkit/RunCommandOnExamplesTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/RunCommandOnExamplesTest.scala @@ -4,28 +4,23 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ossuminc.riddl.testkit +package com.ossuminc.riddl.commands /** Unit Tests For RunCommandOnExamplesTest */ -import com.ossuminc.riddl.command.CommandOptions -import com.ossuminc.riddl.command.CommandPlugin +import com.ossuminc.riddl.utils.{Logger, PathUtils, SysLogger, Zip} import com.ossuminc.riddl.language.CommonOptions -import com.ossuminc.riddl.language.Messages.Messages -import com.ossuminc.riddl.language.Messages.errors -import com.ossuminc.riddl.language.Messages.warnings +import com.ossuminc.riddl.language.Messages.{Messages, errors, warnings} import com.ossuminc.riddl.passes.PassesResult -import com.ossuminc.riddl.utils.{Logger, PathUtils, SysLogger, Zip} +import com.ossuminc.riddl.command.{Command, CommandOptions} +import com.ossuminc.riddl.commands.Commands import org.apache.commons.io.FileUtils -import org.apache.commons.io.filefilter.DirectoryFileFilter -import org.apache.commons.io.filefilter.NotFileFilter -import org.apache.commons.io.filefilter.TrueFileFilter +import org.apache.commons.io.filefilter.{DirectoryFileFilter, NotFileFilter, TrueFileFilter} import org.scalatest.* import org.scalatest.matchers.must.Matchers import org.scalatest.wordspec.AnyWordSpec import java.net.URL -import java.nio.file.Files -import java.nio.file.Path +import java.nio.file.{Files, Path} import scala.annotation.unused import scala.jdk.CollectionConverters.IteratorHasAsScala @@ -42,7 +37,7 @@ import scala.jdk.CollectionConverters.IteratorHasAsScala * @param commandName * The name of the command to run. */ -abstract class RunCommandOnExamplesTest[OPT <: CommandOptions, CMD <: CommandPlugin[OPT]]( +abstract class RunCommandOnExamplesTest[OPT <: CommandOptions, CMD <: Command[OPT]]( val commandName: String, shouldDelete: Boolean = true ) extends AnyWordSpec @@ -99,7 +94,7 @@ abstract class RunCommandOnExamplesTest[OPT <: CommandOptions, CMD <: CommandPlu name = config.getAbsolutePath.dropRight(suffix.length + 1) yield { if validateTestName(name) then { - CommandPlugin.loadCandidateCommands(config.toPath) match { + Commands.loadCandidateCommands(config.toPath) match { case Right(commands) => if commands.contains(commandName) then { Right(f(name, config.toPath)) @@ -131,7 +126,7 @@ abstract class RunCommandOnExamplesTest[OPT <: CommandOptions, CMD <: CommandPlu case Some(folder) => folder.listFiles.toSeq.find(fName => fName.getName == folderName + ".conf") match { case Some(config) => - CommandPlugin.loadCandidateCommands(config.toPath).flatMap { commands => + Commands.loadCandidateCommands(config.toPath).flatMap { commands => if commands.contains(commandName) then f(folderName, config.toPath) else @@ -157,7 +152,7 @@ abstract class RunCommandOnExamplesTest[OPT <: CommandOptions, CMD <: CommandPlu val results = forEachConfigFile { case (name, path) => val outputDir = outDir.resolve(name) - val result = CommandPlugin.runCommandNamed( + val result = Commands.runCommandNamed( commandName, path, logger, @@ -178,7 +173,7 @@ abstract class RunCommandOnExamplesTest[OPT <: CommandOptions, CMD <: CommandPlu val errors = messages.justErrors if errors.nonEmpty then { fail(s"Test case $name failed:\n${errors.format}") - } else { info(messages.format) } + } else { info(errors.format) } } } } @@ -188,7 +183,7 @@ abstract class RunCommandOnExamplesTest[OPT <: CommandOptions, CMD <: CommandPlu def runTest(folderName: String): Unit = { forAFolder(folderName) { case (name, config) => val outputDir = outDir.resolve(name) - val result = CommandPlugin.runCommandNamed( + val result = Commands.runCommandNamed( commandName, config, logger, diff --git a/testkit/src/main/scala/com/ossuminc/riddl/testkit/RunCommandSpecBase.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/RunCommandSpecBase.scala similarity index 70% rename from testkit/src/main/scala/com/ossuminc/riddl/testkit/RunCommandSpecBase.scala rename to commands/src/test/scala/com/ossuminc/riddl/commands/RunCommandSpecBase.scala index 03afffada..f721a5837 100644 --- a/testkit/src/main/scala/com/ossuminc/riddl/testkit/RunCommandSpecBase.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/RunCommandSpecBase.scala @@ -4,17 +4,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ossuminc.riddl.testkit +package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandPlugin import org.scalatest.* -import org.scalatest.wordspec.AnyWordSpec import org.scalatest.matchers.must.Matchers +import org.scalatest.wordspec.AnyWordSpec /** A base class for specs that just want to run a command */ abstract class RunCommandSpecBase extends AnyWordSpec with Matchers { def runWith( commandArgs: Seq[String] - ): Assertion = { CommandPlugin.runMain(commandArgs.toArray) must be(0) } + ): Assertion = { Commands.runMain(commandArgs.toArray) must be(0) } } diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/RunHugoOnExamplesTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/RunHugoOnExamplesTest.scala index 613e3c027..0097448ed 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/RunHugoOnExamplesTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/RunHugoOnExamplesTest.scala @@ -6,10 +6,9 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.{CommandOptions, CommandPlugin} -import com.ossuminc.riddl.passes.PassesResult -import com.ossuminc.riddl.testkit.RunCommandOnExamplesTest import com.ossuminc.riddl.hugo.HugoPass +import com.ossuminc.riddl.passes.PassesResult +import com.ossuminc.riddl.commands.RunCommandOnExamplesTest import org.scalatest.Assertion import java.nio.file.{Files, Path} diff --git a/commands/src/test/scala/com/ossuminc/riddl/commands/StatsCommandTest.scala b/commands/src/test/scala/com/ossuminc/riddl/commands/StatsCommandTest.scala index c446c7388..f30ea935e 100644 --- a/commands/src/test/scala/com/ossuminc/riddl/commands/StatsCommandTest.scala +++ b/commands/src/test/scala/com/ossuminc/riddl/commands/StatsCommandTest.scala @@ -1,13 +1,13 @@ package com.ossuminc.riddl.commands -import com.ossuminc.riddl.command.CommandTestBase +import com.ossuminc.riddl.commands.CommandTestBase import java.nio.file.Path /** Unit Tests For StatsCommandTest */ class StatsCommandTest extends CommandTestBase("commands/src/test/input") { - val inputFile = "testkit/src/test/input/rbbq.riddl" + val inputFile = "commands/src/test/input/rbbq.riddl" "StatsCommand" should { "run correctly" in { @@ -16,7 +16,7 @@ class StatsCommandTest extends CommandTestBase("commands/src/test/input") { } "read stats option" in { - val expected = StatsCommand.Options(Some(Path.of(s"$inputDir/stats.riddl"))) + val expected = StatsCommand.Options(Some(Path.of(s"stats.riddl"))) check(new StatsCommand, expected) } } diff --git a/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/C4Diagram.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/C4Diagram.scala new file mode 100644 index 000000000..5ac9b188d --- /dev/null +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/C4Diagram.scala @@ -0,0 +1,9 @@ +package com.ossuminc.riddl.diagrams.mermaid + +import com.ossuminc.riddl.passes.PassesResult +import scalajs.js.annotation.* + +@JSExportTopLevel("C4Diagram") +class C4Diagram (passesResult: PassesResult) { + +} diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/ContextMapDiagram.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/ContextMapDiagram.scala similarity index 82% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/ContextMapDiagram.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/ContextMapDiagram.scala index 30661d3aa..f97d84be4 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/ContextMapDiagram.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/ContextMapDiagram.scala @@ -1,7 +1,9 @@ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid -import com.ossuminc.riddl.analyses.ContextDiagramData import com.ossuminc.riddl.language.AST.{Context, Definition, Processor} +import com.ossuminc.riddl.passes.diagrams.ContextDiagramData + +import scalajs.js.annotation.* /** Context Diagram generator using a DataFlow Diagram from Mermaid * @@ -10,7 +12,7 @@ import com.ossuminc.riddl.language.AST.{Context, Definition, Processor} * @param data * The data collected by the ((Diagrams Pass)) for this diagram. */ - +@JSExportTopLevel("ContextMapDiagram") case class ContextMapDiagram(context: Context, data: ContextDiagramData) extends FlowchartDiagramGenerator(s"Context Map For ${context.identify}", "TB") { diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/DataFlowDiagram.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/DataFlowDiagram.scala similarity index 90% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/DataFlowDiagram.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/DataFlowDiagram.scala index 827ec60b2..c4f3e2060 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/DataFlowDiagram.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/DataFlowDiagram.scala @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid import com.ossuminc.riddl.language.AST.* import com.ossuminc.riddl.passes.PassesResult @@ -12,6 +12,8 @@ import com.ossuminc.riddl.passes.symbols.SymbolsPass import com.ossuminc.riddl.passes.resolve.ResolutionPass import com.ossuminc.riddl.utils.FileBuilder +import scala.scalajs.js.annotation.* + /** Generate a data flow diagram Like this: * {{{ * flowchart TD @@ -25,6 +27,7 @@ import com.ossuminc.riddl.utils.FileBuilder * @param pr * The PassesResult from running the standard passes to obtain all the collected ideas. */ +@JSExportTopLevel("DataFlowDiagram") case class DataFlowDiagram(pr: PassesResult) extends FileBuilder { require(pr.hasOutputOf(SymbolsPass.name)) @@ -50,17 +53,17 @@ case class DataFlowDiagram(pr: PassesResult) extends FileBuilder { case _: Processor[?] => "[{" -> "}]" case _: Definition => "[" -> "]" } - addIndent(s"${definition.id.value}$left\"$id\"$right") + addLine(s"${definition.id.value}$left\"$id\"$right") case _ => - addIndent(s"${definition.id.value}") + addLine(s"${definition.id.value}") } } private[mermaid] def makeConnection(from: Outlet, to: Inlet, thick: Boolean, how: String): Unit = { val fromName = from.id.value val toName = to.id.value - if thick then addIndent(s"$fromName == $how ==> $toName") - else addIndent(s"$fromName -- $how --> $toName") + if thick then addLine(s"$fromName == $how ==> $toName") + else addLine(s"$fromName -- $how --> $toName") } private[mermaid] def participants(connector: Connector): Seq[Definition] = { diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/DomainMapDiagram.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/DomainMapDiagram.scala similarity index 66% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/DomainMapDiagram.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/DomainMapDiagram.scala index 57c30af1f..e0b417af9 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/DomainMapDiagram.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/DomainMapDiagram.scala @@ -1,8 +1,11 @@ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid -import com.ossuminc.riddl.analyses.DomainDiagramData -import com.ossuminc.riddl.language.AST.{Definition, Context, Domain, Processor} +import com.ossuminc.riddl.language.AST.{Context, Definition, Domain, Processor} +import com.ossuminc.riddl.passes.diagrams.DomainDiagramData +import scala.scalajs.js.annotation.* + +@JSExportTopLevel("DomainMapDiagram") class DomainMapDiagram(domain: Domain) extends FlowchartDiagramGenerator(s"Map For ${domain.identify}", "TB") { diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/EntityRelationshipDiagram.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/EntityRelationshipDiagram.scala similarity index 94% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/EntityRelationshipDiagram.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/EntityRelationshipDiagram.scala index 5528d8e4c..9031dd622 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/EntityRelationshipDiagram.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/EntityRelationshipDiagram.scala @@ -1,9 +1,12 @@ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid import com.ossuminc.riddl.language.AST.* import com.ossuminc.riddl.passes.PassesResult import com.ossuminc.riddl.passes.resolve.ReferenceMap +import scala.scalajs.js.annotation.* + +@JSExportTopLevel("EntityRelationshipDiagram") class EntityRelationshipDiagram(refMap: ReferenceMap) { private def makeTypeName( @@ -53,7 +56,7 @@ class EntityRelationshipDiagram(refMap: ReferenceMap) { case _: EntityReferenceTypeExpression => from + " ||--|| " + typeName case _: UniqueId => from + " ||--|| " + typeName case _ => "" - connector + " : references" + connector + " : references" else typeName } diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/FlowchartDiagramGenerator.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/FlowchartDiagramGenerator.scala similarity index 98% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/FlowchartDiagramGenerator.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/FlowchartDiagramGenerator.scala index 43a21d21f..64c0b4831 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/FlowchartDiagramGenerator.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/FlowchartDiagramGenerator.scala @@ -1,6 +1,7 @@ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid import com.ossuminc.riddl.language.AST.{Definition, NamedValue, Processor, VitalDefinition} +import scalajs.js.annotation.* /** Flowchart generator abstraction * Example output: * {{{ * --- * title: "Context Diagram For Domain Foo" * init: * * theme: dark * flowchartConfig: * defaultRenderer: dagre * width: 100% * --- * flowchart LR * classDef default @@ -27,8 +28,10 @@ trait FlowchartDiagramGenerator( frontMatter() addLine(s"flowchart $direction") incr // indent the content from subclass + def kind: String = "flowchart" + @JSExport def frontMatterItems: Map[String, String] = Map( "defaultRenderer" -> s"$renderer", "width" -> "100%", diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/MermaidDiagramGenerator.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/MermaidDiagramGenerator.scala similarity index 92% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/MermaidDiagramGenerator.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/MermaidDiagramGenerator.scala index 29e88d548..40a90a506 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/MermaidDiagramGenerator.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/MermaidDiagramGenerator.scala @@ -1,16 +1,21 @@ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid import com.ossuminc.riddl.utils.FileBuilder import com.ossuminc.riddl.language.AST.* import com.ossuminc.riddl.language.KnownOption.* +import scala.scalajs.js.annotation.* + /** Common trait for things that generate mermaid diagrams */ trait MermaidDiagramGenerator extends FileBuilder { + @JSExport def generate: Seq[String] = toLines + @JSExport def title: String + @JSExport def kind: String protected def frontMatterItems: Map[String, String] diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/RootOverviewDiagram.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/RootOverviewDiagram.scala similarity index 94% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/RootOverviewDiagram.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/RootOverviewDiagram.scala index a771d92a5..f01623c91 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/RootOverviewDiagram.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/RootOverviewDiagram.scala @@ -1,8 +1,11 @@ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid import com.ossuminc.riddl.language.AST.{Domain, Root, VitalDefinition} import com.ossuminc.riddl.language.AST +import scala.scalajs.js.annotation.* + +@JSExportTopLevel("RootOverviewDiagram$") object RootOverviewDiagram { private val containerStyles: Seq[String] = Seq( "font-size:1pc,fill:#000088,stroke:black,stroke-width:6,border:solid,color:white,margin-top:36px", @@ -14,6 +17,7 @@ object RootOverviewDiagram { ) } +@JSExportTopLevel("RootOverviewDiagram") class RootOverviewDiagram(root: Root) extends FlowchartDiagramGenerator("Root Overview", "TB", "elk") { private val topLevelDomains = AST.getTopLevelDomains(root).sortBy(_.id.value) diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/SequenceDiagramGenerator.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/SequenceDiagramGenerator.scala similarity index 84% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/SequenceDiagramGenerator.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/SequenceDiagramGenerator.scala index a6560ba86..16510dbef 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/SequenceDiagramGenerator.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/SequenceDiagramGenerator.scala @@ -1,4 +1,4 @@ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid /** A mermaid diagram generator for making sequence diagrams */ diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/UseCaseDiagram.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/UseCaseDiagram.scala similarity index 95% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/UseCaseDiagram.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/UseCaseDiagram.scala index c06a7746f..5ba136aed 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/UseCaseDiagram.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/UseCaseDiagram.scala @@ -4,20 +4,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid -import com.ossuminc.riddl.analyses.UseCaseDiagramData import com.ossuminc.riddl.language.AST.* import com.ossuminc.riddl.passes.PassesResult +import com.ossuminc.riddl.passes.diagrams.UseCaseDiagramData import com.ossuminc.riddl.utils.FileBuilder - import scala.reflect.ClassTag +import scala.scalajs.js.annotation.* /** A class to generate the sequence diagrams for an Epic's Use Case * @param ucdd * The UseCaseDiagramData from the DiagramsPass for this */ +@JSExportTopLevel("UseCaseDiagram") case class UseCaseDiagram(sds: UseCaseDiagramSupport, ucdd: UseCaseDiagramData) extends SequenceDiagramGenerator { private val participants: Seq[Definition] = ucdd.actors.values.toSeq.sortBy(_.kind) diff --git a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/UseCaseDiagramSupport.scala b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/UseCaseDiagramSupport.scala similarity index 85% rename from hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/UseCaseDiagramSupport.scala rename to diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/UseCaseDiagramSupport.scala index 44417730c..ff64c396b 100644 --- a/hugo/src/main/scala/com/ossuminc/riddl/hugo/mermaid/UseCaseDiagramSupport.scala +++ b/diagrams/shared/src/main/scala/com/ossuminc/riddl/diagrams/mermaid/UseCaseDiagramSupport.scala @@ -4,20 +4,24 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ossuminc.riddl.hugo.mermaid +package com.ossuminc.riddl.diagrams.mermaid import com.ossuminc.riddl.language.AST.* import com.ossuminc.riddl.passes.PassesResult import scala.reflect.ClassTag +import scala.scalajs.js.annotation.* /** A trait to be implemented by the user of UseCaseDiagram that provides information that can only be provided from * outside UseCaseDiagram itself. Note that the PassesResult from running the standard passes is required. */ trait UseCaseDiagramSupport { + @JSExport def passesResult: PassesResult + @JSExport def makeDocLink(definition: NamedValue): String + @JSExport def getDefinitionFor[T <: Definition: ClassTag](pathId: PathIdentifier, parent: Definition): Option[T] = { passesResult.refMap.definitionOf[T](pathId, parent) } diff --git a/doc/src/main/hugo/config.toml b/doc/src/main/hugo/config.toml index 0f018c060..f092ca6a9 100644 --- a/doc/src/main/hugo/config.toml +++ b/doc/src/main/hugo/config.toml @@ -9,15 +9,10 @@ description = "Technical documentation for RIDDL" homepage = "https://riddl.tech/" demosite = "https://riddl.tech/" tags = ["docs", "documentation", "responsive", "simple", "riddl"] -min_version = "0.94.2" +min_version = "0.112.0" # googleAnalytics = "" # example : UA-123-45 #disableLanguages = [] # disable language from here -#[module] -#[[module.imports]] -#path = "https://github.com/thegeeklab/hugo-geekdoc" -#disable=false - [params.author] name = "Reid Spencer" email = "reid@ossuminc.com" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/.linkcheckignore b/doc/src/main/hugo/themes/hugo-geekdoc/.linkcheckignore deleted file mode 100644 index bc7e6b180..000000000 --- a/doc/src/main/hugo/themes/hugo-geekdoc/.linkcheckignore +++ /dev/null @@ -1,2 +0,0 @@ -.*/fonts/KaTeX_.*.ttf -https://github.com/thegeeklab/hugo-geekdoc/edit/main/.* diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/README.md b/doc/src/main/hugo/themes/hugo-geekdoc/README.md index 8ab84a8b0..99358d83c 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/README.md +++ b/doc/src/main/hugo/themes/hugo-geekdoc/README.md @@ -1,7 +1,7 @@ # Geekdoc -[![Build Status](https://img.shields.io/drone/build/thegeeklab/hugo-geekdoc?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/hugo-geekdoc) -[![Hugo Version](https://img.shields.io/badge/hugo-0.93-blue.svg)](https://gohugo.io) +[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/hugo-geekdoc/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/hugo-geekdoc) +[![Hugo Version](https://img.shields.io/badge/hugo-0.112-blue.svg)](https://gohugo.io) [![GitHub release](https://img.shields.io/github/v/release/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/releases/latest) [![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors) [![License: MIT](https://img.shields.io/github/license/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE) @@ -16,7 +16,7 @@ This theme is subject to a CI driven build and release process common for softwa Due to the fact that `webpack` and `npm scripts` are used as pre-processors, the theme cannot be used from the main branch by default. If you want to use the theme from a cloned branch instead of a release tarball you'll need to install `webpack` locally and run the build script once to create all required assets. -```Shell +```shell # install required packages from package.json npm install diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/VERSION b/doc/src/main/hugo/themes/hugo-geekdoc/VERSION index 84ee7160a..0131a133c 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/VERSION +++ b/doc/src/main/hugo/themes/hugo-geekdoc/VERSION @@ -1 +1 @@ -v0.35.2 +v0.47.0 diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/config.json b/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/config.json index 693579395..1a5582a2e 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/config.json +++ b/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/config.json @@ -2,6 +2,7 @@ {{- $searchData := resources.Get "search/data.json" | resources.ExecuteAsTemplate $searchDataFile . | resources.Minify -}} { "dataFile": {{ $searchData.RelPermalink | jsonify }}, - "indexConfig": {{ .Site.Params.GeekdocSearchConfig | jsonify }}, - "showParent": {{ if .Site.Params.GeekdocSearchShowParent }}true{{ else }}false{{ end }} + "indexConfig": {{ .Site.Params.geekdocSearchConfig | jsonify }}, + "showParent": {{ if .Site.Params.geekdocSearchShowParent }}true{{ else }}false{{ end }}, + "showDescription": {{ if .Site.Params.geekdocSearchshowDescription }}true{{ else }}false{{ end }} } diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/data.json b/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/data.json index 26f24633c..f1c0e804e 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/data.json +++ b/doc/src/main/hugo/themes/hugo-geekdoc/assets/search/data.json @@ -1,12 +1,13 @@ [ - {{ range $index, $page := (where .Site.Pages "Params.GeekdocProtected" "ne" true) }} + {{ range $index, $page := (where .Site.Pages "Params.geekdocProtected" "ne" true) }} {{ if ne $index 0 }},{{ end }} { "id": {{ $index }}, "href": "{{ $page.RelPermalink }}", "title": {{ (partial "utils/title" $page) | jsonify }}, "parent": {{ with $page.Parent }}{{ (partial "utils/title" .) | jsonify }}{{ else }}""{{ end }}, - "content": {{ $page.Plain | jsonify }} + "content": {{ $page.Plain | jsonify }}, + "description": {{ $page.Summary | plainify | jsonify }} } {{ end }} ] diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/assets/sprites/geekdoc.svg b/doc/src/main/hugo/themes/hugo-geekdoc/assets/sprites/geekdoc.svg index 89a556da2..4f3cfd291 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/assets/sprites/geekdoc.svg +++ b/doc/src/main/hugo/themes/hugo-geekdoc/assets/sprites/geekdoc.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/data/assets.json b/doc/src/main/hugo/themes/hugo-geekdoc/data/assets.json index b7666e5d7..0f61adf64 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/data/assets.json +++ b/doc/src/main/hugo/themes/hugo-geekdoc/data/assets.json @@ -1,506 +1,434 @@ { - "css.js": { - "src": "js/css-a85b7f60.bundle.min.js", - "integrity": "sha512-UJu1Wxqkbc0wqfivVuIbrh1o40ut2iYQkx2S8gvzDhLfO7Zj2n9PSZlAzKGsj2/DyzPmUUKs+ALGlppvrKiMlw==" - }, "main.js": { - "src": "js/main-902b82d5.bundle.min.js", - "integrity": "sha512-KfjmJn9/csyFT9kE/7rim3xU19ul/7X1pIPDtJmLXDMAuVgmECTwXU2q7SOnfNZH3AV5wjfTHsxmSZQ8L9I7nw==" + "src": "js/main-09678822.bundle.min.js", + "integrity": "sha512-GCYuTkVsjEdMpK2AQuS/pHOOJ5DrXO03gkTYclc0FP8P7L0FEa0QKA43bVT41mISGSj9ILCP1yuM1LeeNxQqIA==" + }, + "colortheme.js": { + "src": "js/colortheme-5cd55a83.bundle.min.js", + "integrity": "sha512-LSPeKNXq7s3OsNuiZNWMLtHQiosK5c5n4YKcs24Gpj0Y5IVwZu2sAHZdXn6i23hT6NjDCpqNkAIGmIxS4trhYQ==" }, "mermaid.js": { - "src": "js/mermaid-4aacc7a5.bundle.min.js", - "integrity": "sha512-E4ioGopFKK2mNj6IYmgdEbOIuCqi9CJN143IPCWndcxYbdu9xl7f20ulQjYfRwTBx7MQVrIBqLq3k0Mfd/8oSQ==" + "src": "js/mermaid-3ef5414c.bundle.min.js", + "integrity": "sha512-arDXAwNWNuOEooRIkl5nF0ygTbh+AIfBUQnIz/RBQjDX+KGVZc2bJDTgs8qvuwt1PkJmEi9EyL7f3kMywwKEyw==" }, "katex.js": { - "src": "js/katex-8d0741cb.bundle.min.js", - "integrity": "sha512-25Qlbky4CO9ETeYAQAmBSOT32hziFxk1Z/A8/+2IQtqK1orAS5CcnGG3yKJGDLJHzHRxY/6k8tA7XXz6M8WAsA==" + "src": "js/katex-1983cc83.bundle.min.js", + "integrity": "sha512-ZySFoehcoCQmI2dTHJU6FJy2LAL6IydddoNm62hkJ151tgC5Y6L81jPcbA2n2qKueyA/U4ODv/fvhRJXp+DT1w==" }, "search.js": { - "src": "js/search-1c4cfb2d.bundle.min.js", - "integrity": "sha512-wK0vKlf8b3uje5GnRC4RN0aL7HzWDu7slqHlbfqCmCbHW30ede6D5H4rc/VqrpOx/Pf1ZfTV5L4Tx4pka1TwXQ==" + "src": "js/search-49cf88d4.bundle.min.js", + "integrity": "sha512-F9UY++rD6ugV6dTRejwnZNhZwbgi1T0as8CXppSaODHFcwFB4u/15CGaZ58jc1QA2PZCiIfe4pLJsK8CgHDYGQ==" }, - "js/273-647fed29.chunk.min.js": { - "src": "js/273-647fed29.chunk.min.js", - "integrity": "sha512-ccb2FynPdwxbAu0R6doHLCzhykkAcN1PkbPRLCTSXSFqyyW07ARtug3GOg6uayq9EHnDwNSKY/PX2vx3lK4CXw==" + "js/130-a8f5fd5a.chunk.min.js": { + "src": "js/130-a8f5fd5a.chunk.min.js", + "integrity": "sha512-ZLr/UBC/pVrIxw46fL3qTIupe7MIf3ZcAOarENTRnxAGIGog3BUy5Pf4en5dwuXexkmerFqNNK22UbFZ1JSTrg==" }, - "js/116-9febb0f9.chunk.min.js": { - "src": "js/116-9febb0f9.chunk.min.js", - "integrity": "sha512-/Q9vU1uEeKauM/gIWkGW3dePuGUxMe3azDJyThwmV8lHAQU+g08KYYF3mzAeOjHtZCI3ZLzAMHlznQgrWx3mrg==" + "js/946-1b52cc58.chunk.min.js": { + "src": "js/946-1b52cc58.chunk.min.js", + "integrity": "sha512-6DMQSQRfI95J4r0L1F7G1Iq0eLZE576b6GKKiG5zyEuEjhUTgbYhdcxQ6JFIb0Tde5bri0++HEvtAX9+M5tRgQ==" }, - "fonts/LiberationSans-Italic.woff": { - "src": "fonts/LiberationSans-Italic.woff", - "integrity": "sha512-3rg7qqlEgAeip3NcoxqNNKeVrPvkXCxHbybcidDz8/aKmNhtp9LG45K20dOaOxvWrB+XbjM6bBPnRuzJj8Pltw==" + "js/469-f2469208.chunk.min.js": { + "src": "js/469-f2469208.chunk.min.js", + "integrity": "sha512-0XIvTRVgo9xdg4Xk/GVW2WuXWGXQ9DwhEmyb+3Wz9uvCOdMbMsmigEIyyMJllVvxP2Cgru04XCkd6Fs5TyamaQ==" }, - "fonts/LiberationSans-BoldItalic.woff": { - "src": "fonts/LiberationSans-BoldItalic.woff", - "integrity": "sha512-l+QH9jdBUO/jvAiX27bbZvr5vCiPwBt1IJqfTy3545wRaqGOP2qeFNvolbaj7kIS7d0rc841Lgf7NACrcMFCmQ==" + "js/453-841ef5d0.chunk.min.js": { + "src": "js/453-841ef5d0.chunk.min.js", + "integrity": "sha512-MFbK3w3smpbKZOwL7b5LC0r9XMifZtCPznNa6OjYE//6wJ330/yyN9hQH/+ut2Tbb0r13+Ipfl2a+bMp9xqmHg==" }, - "fonts/LiberationSans-Bold.woff": { - "src": "fonts/LiberationSans-Bold.woff", - "integrity": "sha512-dcvCYm+u+bCFKnERGNyS94DBqaNaaXr7TdD6cNXNvCwNV1jk7mOnRXub3rjX2hoIEcyMSBbeIny9nP5QCBij2g==" + "js/361-fd12fb6b.chunk.min.js": { + "src": "js/361-fd12fb6b.chunk.min.js", + "integrity": "sha512-N/i/eX3bGOXpy59x+D7h1UtnfkhUKH1ErsudMMP41pRHqKTxBhHMVr9CefUEOKp3eFWzugGEKfLgKm7XR0EQ8Q==" }, - "fonts/LiberationSans.woff": { - "src": "fonts/LiberationSans.woff", - "integrity": "sha512-X8iWtp7gsJFHLyWhQzM8IZMH97LUsxhB5Hzv9smPHsqmRrDhl/S5xClHq3lUEtupVjCxcthMXl2qQvXcM3XVkA==" + "js/943-2329fd45.chunk.min.js": { + "src": "js/943-2329fd45.chunk.min.js", + "integrity": "sha512-7GsQ7jL8fmK3idqKsxebCKsUVE/PLUN60JfUq0mrJgOvRCJ9onF48LTNja+xcE6lt8U5fgXu90TU4ihKB/xAYw==" }, - "favicon/apple-touch-startup-image-2732x2048.png": { - "src": "favicon/apple-touch-startup-image-2732x2048.png", - "integrity": "sha512-520ICJWMPSzTibQuHKpyYHgznwGlf3T95MTLoETOEpTzuxSYelNYNJhcYIhyv6I0r+PtLXNDfxMaIGAJewU2Dw==" + "js/869-e12842bc.chunk.min.js": { + "src": "js/869-e12842bc.chunk.min.js", + "integrity": "sha512-wc+Eymyyq7An5vIKmjthlXdjDeK4WlEkhbt3MvLRONP7qQVB4RVa3Me7d7arK5eRdzHCpKGi9idoPm9NTCup2w==" + }, + "js/843-3cad57ab.chunk.min.js": { + "src": "js/843-3cad57ab.chunk.min.js", + "integrity": "sha512-asnZIebCk7tlrq2AF7TUV9CKuy98QKSq/xYAhglS6HOHVBYDXDGyVtrctV7Fj0D/K2Lf6jZ8TFGQSylksxnrNA==" + }, + "js/803-1eafdda4.chunk.min.js": { + "src": "js/803-1eafdda4.chunk.min.js", + "integrity": "sha512-ZqqD4WkSEMthlUDLXC8zwtvIXCekJvZsg9iqEk1yd5iCZm0IZrcAYq8pftp90B9T9xVKsYMgL7Dn5HnbJdB2cA==" + }, + "js/478-2cf0f688.chunk.min.js": { + "src": "js/478-2cf0f688.chunk.min.js", + "integrity": "sha512-gtcC/mc5DFo/SSaN3NA+qEBL190b5JI/uFpWF2QDVzuU3ZxQnWMjeyzMT5u5OUOMQd26U+cRybl8N+fYD5PLgA==" + }, + "js/925-46d6527e.chunk.min.js": { + "src": "js/925-46d6527e.chunk.min.js", + "integrity": "sha512-rxgu2Q8iOZVwEFIlxI0wtb80OfmEjqG+yfWT3YHyKGUEUQa++07cZCBLQ3p/g8Gu4z8ZCk6QV9luBxHvrrpljg==" + }, + "js/706-7d8b0e60.chunk.min.js": { + "src": "js/706-7d8b0e60.chunk.min.js", + "integrity": "sha512-JuRNj/UTpUsVZrv/SH/AQAZrQvRoczz/L57/0OKD4i1qES/Mh1goL9lgh6YKywPHwy/25KnBkRc5+FZ0binxvw==" + }, + "js/689-a6543b15.chunk.min.js": { + "src": "js/689-a6543b15.chunk.min.js", + "integrity": "sha512-t2zhb5BzgRTBeIGZ/LWx7Lj6lMjVVRvQBC3BQZLUnKL63OP0bgoqHKIXIWAZLEYoRVqAzumIs99m9aGWN6B9XA==" + }, + "js/426-d392b5f7.chunk.min.js": { + "src": "js/426-d392b5f7.chunk.min.js", + "integrity": "sha512-uo3+Arelp4r/EyT4dZYx6/WCpcfnFMhbfz4WH/iXe98IzDbu/eAiNU+leb3Nrwd4i/pjLks+9kfjf/NPkYWsKA==" + }, + "js/635-51e27587.chunk.min.js": { + "src": "js/635-51e27587.chunk.min.js", + "integrity": "sha512-n1FVnHtRwljJV9Q75Pqnv5uhiWUYCcGGKaKzO8GO8uLc0vOXFaZ5q2zAicYQIpbANz8OxKGagjYnc5iua+IltA==" + }, + "js/331-abf8602d.chunk.min.js": { + "src": "js/331-abf8602d.chunk.min.js", + "integrity": "sha512-DDzkZxxhbY5Cf9s2KsqXHEmp1emK8RkpB+X1+73uqtYSGMH1MapzWh3zdx4CpR/DDYNVg/Gf/Q/0hGcEacfSlA==" + }, + "js/68-103d5339.chunk.min.js": { + "src": "js/68-103d5339.chunk.min.js", + "integrity": "sha512-K6XYGdsXrC/6y4E8rLDqcTeTECYP/1dn993zSDl7ltlzneerIzm0akOmlZX3FDid8ijSi7CNULlNPZQpbOn9Kw==" + }, + "js/420-4dcd7780.chunk.min.js": { + "src": "js/420-4dcd7780.chunk.min.js", + "integrity": "sha512-8FkVhFyMIvw2uA+HszrjrCdrv6wQHOKJ0uxIyutMFZTvjVtO87DCvt8Ahv9qqzaffensLm/11+hAjIzCTTd5Yg==" + }, + "js/741-decd4dbd.chunk.min.js": { + "src": "js/741-decd4dbd.chunk.min.js", + "integrity": "sha512-SieG1mmlbY8BojwRMaORnD0DVFGpaelrQOxt72NNaQ/AGJYCTv0UTlwiYhQ729MkCpgae/hobLwPyeJqPNfvKA==" + }, + "js/376-8acf1e69.chunk.min.js": { + "src": "js/376-8acf1e69.chunk.min.js", + "integrity": "sha512-NAB7Uh5Ct2psMDB4zXfT44hs0i/1BrLLoM+8WTk5Gonh76UogDHIQ0nQt2iGsvG8gZ0X+2e4Jsr4PXEIuIQVcA==" + }, + "js/229-f9d2d88a.chunk.min.js": { + "src": "js/229-f9d2d88a.chunk.min.js", + "integrity": "sha512-VVh3MPlcuvSFz8xWQ1vP8QyNt9Qb875S2kQXnVOM1rVw1EjK42d3K6JgVwUBwV2Bafc2LrCsmAuZAlx+57k+2g==" + }, + "js/199-d90790f5.chunk.min.js": { + "src": "js/199-d90790f5.chunk.min.js", + "integrity": "sha512-MDsGCdohyI85qYnSq2pNWzLnCxfGzDnZdS+F761+cW8k/P8+P7s4U6W1ric31pFIW7v/ANYWSstR9FDFia8iJA==" + }, + "js/162-a1ab78bd.chunk.min.js": { + "src": "js/162-a1ab78bd.chunk.min.js", + "integrity": "sha512-wI6IXoLZMwe/Fp3R0ihm4Xr4gDblKL4Ys6Yeg60HoJGYGx+DOu+76g8WVFITcbUkFi1UiJDZpQYXin8CffZ43Q==" + }, + "js/194-be4cbb11.chunk.min.js": { + "src": "js/194-be4cbb11.chunk.min.js", + "integrity": "sha512-II36WdnnYF/FFhR4UjmKTFRoI/GrxukTQkymBeUrU/gZPJg34jr8e3FgUXZNvFl2vU7Nj30M3qb8izhXLVPffA==" + }, + "js/274-ebda0489.chunk.min.js": { + "src": "js/274-ebda0489.chunk.min.js", + "integrity": "sha512-VmmqW5ZNZrmYKkOrhFVvdcT019n80Jb1842RqERTObkjF4oGJU2AfgMrxEyCmaafidZADS8DLnzCsu8B7Wu/jQ==" + }, + "js/944-4463b3c5.chunk.min.js": { + "src": "js/944-4463b3c5.chunk.min.js", + "integrity": "sha512-yEcPG808zIQDxPjw4mRTa+sEry5Cv7wkxZYHpVlwdo4N0T3F9v1pR+Ct95/9jE/dE/wV6NCg4fb4Pu9SwREIYA==" + }, + "js/55-eecfba3f.chunk.min.js": { + "src": "js/55-eecfba3f.chunk.min.js", + "integrity": "sha512-XXyzX6KOiFasf6EvoDumuoLskOvtSaRtsul342L8j0jp7PuzqiNhMj5CPQprnqJcOZtjED/Kp2EVbAVsJeoE3Q==" + }, + "js/983-2d527157.chunk.min.js": { + "src": "js/983-2d527157.chunk.min.js", + "integrity": "sha512-dM7zoeoBOuc02O49iAVHX3KRUyAm63ZX+NdYZGD2uUl31ndcnlW3GpwHZBCQT8gEd8F3LVTW3x+S1Q579MFJrA==" + }, + "js/548-1c05943b.chunk.min.js": { + "src": "js/548-1c05943b.chunk.min.js", + "integrity": "sha512-ktHNAbHwfkuY9B8kkT1bjOAF/GFfE8mI36Xi+1aOcjCdL9IR49pXvTPWHVnREc+pQ5F6BUohf3GgWYlTllzlGw==" + }, + "js/626-4f608392.chunk.min.js": { + "src": "js/626-4f608392.chunk.min.js", + "integrity": "sha512-7S6ohob2+4G0X8FgsYQnPmZqAZUKRSjycTrvlmzNUS3gXYVGUOs1mQeqpPwHwQkU5F4TqzjStMvwgBOUWlrRKA==" + }, + "js/245-61d50476.chunk.min.js": { + "src": "js/245-61d50476.chunk.min.js", + "integrity": "sha512-FVVXOBvY3MTPoT98u6d6IeQtZCiVIcXoF23YSyWLjpsVmk8CcJaFrs2O9T5sluQ6u5k2L0ZDbUkygUKyI1J+cQ==" }, "favicon/apple-touch-startup-image-2048x2732.png": { "src": "favicon/apple-touch-startup-image-2048x2732.png", - "integrity": "sha512-zSQs1F7Hz5qUzsyvq/kOycugg2k1t42QxIPAQIHXC87EwzjPPPewWinVDkqi+GIuGSa1xqzHv3srOCpKmgE0QA==" + "integrity": "sha512-YokE0NKCcDD/prfUCXQEs4vuVJJobwjlFbUV7G0JNWNflpfIvddwFWYi/rpxXmimyxP46hv8jS+5oekNDtWMHw==" }, - "fonts/LiberationMono.woff": { - "src": "fonts/LiberationMono.woff", - "integrity": "sha512-fP8icFlpIzR+72w2iaQLQAImsyFi7T1hjZhT4102/kw2k0EJ8Q4iufSfjxhlKyeh7EAzF8OaEsOKeOmA7MfHVA==" + "favicon/apple-touch-startup-image-2732x2048.png": { + "src": "favicon/apple-touch-startup-image-2732x2048.png", + "integrity": "sha512-nCB9v/9CbzLz8sWSItEAYvUMNiFxL7ue2a9kGVlQxsH7SKbI1WDnElVqz/wmS66mEUPtgCazXEDXmZ61AgEojg==" + }, + "favicon/apple-touch-startup-image-1668x2388.png": { + "src": "favicon/apple-touch-startup-image-1668x2388.png", + "integrity": "sha512-AUEabfLce1eUE4gl5aT+bu1o5xQx1xhKeBDAqDqVzLWIm7GcCAvNzUYFc2Q+8XsRz3xyipeCchx3pcmCptR01A==" + }, + "favicon/apple-touch-startup-image-2388x1668.png": { + "src": "favicon/apple-touch-startup-image-2388x1668.png", + "integrity": "sha512-HElqvqhRP5wNGCkVnq9DK63ote1m9cjGU4D75USjVenObeJC3H5Q52/Htep/KShxP6lbqYmzbZaJXbJn+DR7eA==" }, "favicon/apple-touch-startup-image-2224x1668.png": { "src": "favicon/apple-touch-startup-image-2224x1668.png", - "integrity": "sha512-WXbw/s6qbt56l3Id4IFpLFhVx+otXWyFN/EwpJ5TU+hY5JGA8Ro3f+vOFYXP04Mwdubg5kEqzw/4HzD6uZRMVw==" + "integrity": "sha512-oZUXN3bw5WljJTtAQ6GsjpR1+WFebJ4CRwg49fcQnv4Qnw+MZWOMgtU+dK69mwkwL629Ur0S2tL/njFUaCY9GQ==" }, "favicon/apple-touch-startup-image-1668x2224.png": { "src": "favicon/apple-touch-startup-image-1668x2224.png", - "integrity": "sha512-WCqMLLCyfHzTprEAoaPFaeHwJYEsusnb12rdoLXXbMbXA6v6KKUYDfziu2Z4HQ/34MpGSf7wvxfZss76rrHivg==" - }, - "favicon/apple-touch-startup-image-2388x1668.png": { - "src": "favicon/apple-touch-startup-image-2388x1668.png", - "integrity": "sha512-xrkXrwGMnitt+kmv275t0MBE6S6+zxBZIYo87N3JdEVoq1HdG3PpO1gJZ4FaDWDWBH/1OF6/pYVmtF4QoTActQ==" + "integrity": "sha512-0BLXRqxQg3T8EaP1feuMRtEwGe4mRxN4J/V9DUH4mPfDhO+KlkOQPt5hMSkZgm/iRCN3ontRfUqQuM9o7rNlNw==" }, - "favicon/apple-touch-startup-image-2160x1620.png": { - "src": "favicon/apple-touch-startup-image-2160x1620.png", - "integrity": "sha512-MMXrqQtIn50RcyBDPW/a9lTQ55/ad0cz6x5Ilv3Fv5JRZDgzGwlzJnD6YcNyvod2PCsKgoCAF+KqHa/odyJccQ==" + "favicon/apple-touch-startup-image-1640x2160.png": { + "src": "favicon/apple-touch-startup-image-1640x2160.png", + "integrity": "sha512-NIOwjDakge6LbQOi6yJ95+6fDt8r6G4a/DciK3UpznPon27rcYnHSLzQcjQPhaB3ipq6m4N+0+00xn7UtgqBlQ==" }, - "favicon/apple-touch-startup-image-1668x2388.png": { - "src": "favicon/apple-touch-startup-image-1668x2388.png", - "integrity": "sha512-iZGmjl4HG8f2LKnAIsJpoplLTPbwbpydO+2V/3mI73rSY5haG5JhcdU5ZGzqeuGjSdm11MK5j7w4IZ9VRpyyhQ==" + "favicon/apple-touch-startup-image-2160x1640.png": { + "src": "favicon/apple-touch-startup-image-2160x1640.png", + "integrity": "sha512-sIFsy+zli4JyDj5zBsotkiPpFvF9dsMhpvIgUVE+LqPIwZFYkE2GtKnP+KVNNykAFVsDkcy3QZthcm9KK5/UkQ==" }, "favicon/apple-touch-startup-image-1620x2160.png": { "src": "favicon/apple-touch-startup-image-1620x2160.png", - "integrity": "sha512-D2Ad9ZPnEd1Ld5t6u5ljx3mdRy/VOyR11Qeee4rk1QVTThehfgx2slFfH86o36B6CBHaDZYMrWlMyXS6H8DI0A==" + "integrity": "sha512-RHyTtirEIbYgcbi7o4OA9ERX20kKWPkVUxkOMU4r4QEUs00mdXGFgFcoJi9ppH8zvlQJ+axSe1k0kqglEeeGxg==" }, - "fonts/LiberationSans-Italic.woff2": { - "src": "fonts/LiberationSans-Italic.woff2", - "integrity": "sha512-boZm4ZsUNEmYS85TJvhuBiOUS18gpj0+9WbFgBpAQbCWdU5yde32bVS6rP0YvNvZMuS/R92y+e/bKbcgbMGDtg==" + "favicon/apple-touch-startup-image-2160x1620.png": { + "src": "favicon/apple-touch-startup-image-2160x1620.png", + "integrity": "sha512-8lp0hbQZ0tLs8G5UaHOnFg3B8YCeoB7Hwh+MIrSyPJN5L6bsknvHKh7OJ+CSe8y6bNMw3oaF8McuFZdwAFz8pg==" + }, + "favicon/apple-touch-startup-image-1488x2266.png": { + "src": "favicon/apple-touch-startup-image-1488x2266.png", + "integrity": "sha512-BrZPqZBUqE4P6wGmGstthjdoJS0ZHMH3+ZayiHZ98kU2uFaalUJ06ufYtqwtYGAjt1gm+jRcecWpXQS/dSuctQ==" }, - "fonts/LiberationSans-BoldItalic.woff2": { - "src": "fonts/LiberationSans-BoldItalic.woff2", - "integrity": "sha512-5MVxBiZI9GlXK/F6eeZnwsLBYOMzoQ+ncAmSIoBa+kkrYnMfWaEHJaJO9tA6ml44ety3gt4e9tNmYZULvO86ug==" + "favicon/apple-touch-startup-image-2266x1488.png": { + "src": "favicon/apple-touch-startup-image-2266x1488.png", + "integrity": "sha512-9IY9tbEUW6SA5+SVO7ZPRrHLUhxxWfNjgzzy4zjJoRKs3dkCZjfIblJ/LryGxVn8ADdMsQ95NnoHsrdDl2DNnA==" }, - "fonts/LiberationSans-Bold.woff2": { - "src": "fonts/LiberationSans-Bold.woff2", - "integrity": "sha512-msH61PCwMuCScUPTyVOjuQgZBhYICioAyJxifpioqircJqe1voESkLNzFz6NBmhewRZvfwJHKzwAne1cxg7mpQ==" + "favicon/apple-touch-startup-image-1536x2048.png": { + "src": "favicon/apple-touch-startup-image-1536x2048.png", + "integrity": "sha512-vBTv46G4vFcoEZH1UBNDg6rsFbXq89BJaJxMUAvYJiRg/KBpufEHw1kgWTA2oNCYlNRhMT0hjxNJmmqtu2COwg==" }, "favicon/apple-touch-startup-image-2048x1536.png": { "src": "favicon/apple-touch-startup-image-2048x1536.png", - "integrity": "sha512-JL85dQr6+4HH6oukUWxPs1rbTKe2ZZE+t148UJBE6B4BGy8JYdtDZ8RMnks6vfzDNP7mk58GF1K6vEPZD/O/CQ==" + "integrity": "sha512-OI4gHQ4IldAV8SyUc2ho9SbbYyp+XwDruORa1fQ//ajtHrnOhcJwbHKITDU/txXi9Mu8lNoQF4ZL0KlwbgWACQ==" }, - "fonts/LiberationSans.woff2": { - "src": "fonts/LiberationSans.woff2", - "integrity": "sha512-/se1p5pF9DbDIpOqEIdjqpr1J3v84dQAHPFdMsK1ZiojTlOWQJuqCH4jZ+oZh2K7TtOJa8lyY14RIHTvGh3+SQ==" + "favicon/apple-touch-startup-image-1284x2778.png": { + "src": "favicon/apple-touch-startup-image-1284x2778.png", + "integrity": "sha512-QponzIbyJK4swXUNcRvBOA2PxLVMtpler77JaGFFEekuBLDSEjgqlFQpUU8OP9D7azxV4hbCuo3mIBk+ACVU0A==" }, - "favicon/apple-touch-startup-image-1536x2048.png": { - "src": "favicon/apple-touch-startup-image-1536x2048.png", - "integrity": "sha512-zd8Wn/2cJh9AFShTRz9iMIPIuCHnxOyabursGmH17EbbyEsL66xYP/F2FggCD6vc4/KOk73NQc8UFXLd6ZFx3A==" + "favicon/apple-touch-startup-image-2796x1290.png": { + "src": "favicon/apple-touch-startup-image-2796x1290.png", + "integrity": "sha512-9vFVbbDidzf6kotWFjrbzjX+Hb3e9iMj3dcOoIi4sCPiWs1f7Syje/YHCEAcvexGl/lBmx2dQGeGuh240Dbk5Q==" }, - "fonts/LiberationMono.woff2": { - "src": "fonts/LiberationMono.woff2", - "integrity": "sha512-p5oGo6T78XQ6SECsAez1Sc9HBw0SvLJlhndS+pJ0KyauzBdilh7/8/M/V8ivTjbJKU+rJHtIjHtMUVhPQjXq+g==" + "favicon/apple-touch-startup-image-1290x2796.png": { + "src": "favicon/apple-touch-startup-image-1290x2796.png", + "integrity": "sha512-lPcXLHVgYWacvHsaYIGTBEgEkihk11RA9rOu7GYdaYcwsZ589w9SVPnKz2ZrWbAnumFBJcSbtsjFbnKtUN1Y/g==" }, - "favicon/apple-touch-startup-image-2208x1242.png": { - "src": "favicon/apple-touch-startup-image-2208x1242.png", - "integrity": "sha512-Oei3vVNEzEvSajRyWJV0ZzFPwULEdbGklMa9s59PtFwCEfBU87HCzQfNxGEG7lze2TKftuLZqIqj0N15ZZ8JcA==" + "favicon/apple-touch-startup-image-2778x1284.png": { + "src": "favicon/apple-touch-startup-image-2778x1284.png", + "integrity": "sha512-Y3JKY94vTDTwDyfkevU9SzMFCCwCxc+5E6HXGbWNrDEC2G/pBjfc0Dtj7vjUWwXYkGlvz83mv6lOwyx977qDww==" }, - "favicon/apple-touch-startup-image-1242x2208.png": { - "src": "favicon/apple-touch-startup-image-1242x2208.png", - "integrity": "sha512-rX1o6UJqKhO11hs6wQWOVNns6aafDqcVlPxuLx6TCCgIN4+evdu0M1X/7ZZalxaH4HHcDb3F72OjMw0JrzC8DA==" + "favicon/apple-touch-startup-image-1242x2688.png": { + "src": "favicon/apple-touch-startup-image-1242x2688.png", + "integrity": "sha512-idI9QV970BBuqHVUK3Iz8/VIJIxbUSdsO0lRMhhhuM946UwU6hxkmEALjIAOUVsoNocLx83IDJQ3xFzvJdmqhg==" }, "favicon/apple-touch-startup-image-2688x1242.png": { "src": "favicon/apple-touch-startup-image-2688x1242.png", - "integrity": "sha512-M0IjR8gLlbqqql5/qjIhYyfm/poMOz70jRQIQyL1wYQBCquD4N7G3lm9Mqc3bJP0yvmBGgrflV9fg8sikxjWdw==" + "integrity": "sha512-pheYOnXH6xTfyN6Fu/WWt8Iri0Gi5GYgz+omDYRJDm0Hqvbzj9d5qdSWFNajsP4cq7SIWvufk4HTKM8HRlJcmQ==" }, - "favicon/apple-touch-startup-image-1242x2688.png": { - "src": "favicon/apple-touch-startup-image-1242x2688.png", - "integrity": "sha512-xigXQLupadhWPZNsRFDri8f/Cm4J20shvo/wIM3P+qHIxAcj/kqgRfON/UHr2Lrn6eA11uEGrg8CkngM+F8zbw==" + "favicon/apple-touch-startup-image-1179x2556.png": { + "src": "favicon/apple-touch-startup-image-1179x2556.png", + "integrity": "sha512-pMG0JoxB6oSXTlvDmMqd9eSTw4AKh79yoezlt4rY84c/j0rNq5TDlA9ufFbxlaG7AmmPLuIHjpF/QkxEsvcOVQ==" }, - "favicon/apple-touch-startup-image-2436x1125.png": { - "src": "favicon/apple-touch-startup-image-2436x1125.png", - "integrity": "sha512-Q6kqiD5/zHEEM/XYaSf+VjpBw++Jg3KSSqycIBiVFj2UGwArcxxesmzuCvfbtUmOoKAtso+Bjh38sXrEcrYD/A==" + "favicon/apple-touch-startup-image-1170x2532.png": { + "src": "favicon/apple-touch-startup-image-1170x2532.png", + "integrity": "sha512-7SGtX4osQ9usUAR0Y+Tzhm1yNgbuFqakrfUdpIq1Ew+G3CaKPz7yAIhAOaVzZjx/1845xF2xIuzQDqaUv1MZOg==" + }, + "favicon/apple-touch-startup-image-1242x2208.png": { + "src": "favicon/apple-touch-startup-image-1242x2208.png", + "integrity": "sha512-tt0ce2lqND4xuWki9CvqM5EXGZ7NX4v9RwYIZ9VFD1z3uxqBhWCcCNvVoSxRRWM+dYwsarkPg1K/hx0V3lkP5g==" + }, + "favicon/apple-touch-startup-image-2208x1242.png": { + "src": "favicon/apple-touch-startup-image-2208x1242.png", + "integrity": "sha512-bvniXuoGpDamwT44txRGN9oddmRNAIhmqNxeUknES1xC6i5wyBwREP9sAwPnpEhN331l6Cn5Wb1qdGA9QaLlxw==" + }, + "favicon/apple-touch-startup-image-2556x1179.png": { + "src": "favicon/apple-touch-startup-image-2556x1179.png", + "integrity": "sha512-ITdm5LRh+q3NInMJTTOZoAxYELGjGZ9SpVdNHNp5ybF1qTxX/nkssKXMfAJ/dfm37kyxRPWyQyXQ/KUHJLSocQ==" + }, + "favicon/apple-touch-startup-image-2532x1170.png": { + "src": "favicon/apple-touch-startup-image-2532x1170.png", + "integrity": "sha512-4Qc/I1TX0EHaDimxw8frjq0WFaSvBd+t5pWziJ3j6EgPCjWGjpMS+p2ydudjsztvXi7zpOVWNnSLtt7XsTtFcA==" }, "favicon/apple-touch-startup-image-1125x2436.png": { "src": "favicon/apple-touch-startup-image-1125x2436.png", - "integrity": "sha512-uDKdJPnbrR3sjbZxVTnhUwWFgc02uUgG/Oj4G0sb0jJtcyVShsPBCefDdV1EalLkhZiVrHnSkiiM1hOIQ9aJjg==" + "integrity": "sha512-h9ZTyMSymNZHrZtOpWb5N2DJ4vircBLXwHRJogSUxWTqaaMv1v9niPpGPG+wPPkE385kGt34FvHcUJduQltZdg==" + }, + "favicon/apple-touch-startup-image-2436x1125.png": { + "src": "favicon/apple-touch-startup-image-2436x1125.png", + "integrity": "sha512-zbbCawkNNm/X+dfE9EKwShnCNlgekifcgyizmbWo+hhqzANicUVRr0mdHbiHtiYTvaT9r+5bp82CCGSXV1I92Q==" }, "main.scss": { - "src": "main-be78422f.min.css", - "integrity": "sha512-mcjHR+3cgo9XWy9DXfx0GWbkiN+vALV5kzNvQemlB2b0Y4l2z4c1NUi1rf8/G+oJfdwkUnFrQbuUAMyZPfvPgg==" + "src": "main-c93819e1.min.css", + "integrity": "sha512-8Rgb1yYpTWoY1ehn3iYLL54xVXSYqFMoHc+S0R3Nao02AqnUhxHEcUSDd8xQ2q0IB6TxU7P0QGWAqWLD319dHw==" }, - "favicon/apple-touch-startup-image-1792x828.png": { - "src": "favicon/apple-touch-startup-image-1792x828.png", - "integrity": "sha512-SclwE8AAOyR81/CdPU5XjiybQ9sQhmPht+Sz4/d7PR7gZhpoLEKBj0ovrWNV4xsiVKPhJCsdmkl17UIHftlWHg==" + "favicon/apple-touch-icon-1024x1024.png": { + "src": "favicon/apple-touch-icon-1024x1024.png", + "integrity": "sha512-x4EPwmg4HpFLbCAC4JFhaPNIwuNQCPLfjWqK9ai9I1+oLOhtZbgNbKxjBz/AxFS60IYVhyZvJCyFQMYbIC+SxQ==" }, "favicon/apple-touch-startup-image-828x1792.png": { "src": "favicon/apple-touch-startup-image-828x1792.png", - "integrity": "sha512-UkZGpIoAUN4QBxO8q2qv/dsmyexeOmatgoz5W0sYrcwMyANSXdh/AzkUCATEur+2nDNa7FpycVZ+H7ox9teiww==" + "integrity": "sha512-4cxF+n/FQtSCnoGbBMnp1aeTR3qnW6/DtzowLnlDRpCnSxRjSXOg7AizEx1dw0ICoqN7rQE3/+elGjgog7uxsg==" + }, + "favicon/apple-touch-startup-image-1792x828.png": { + "src": "favicon/apple-touch-startup-image-1792x828.png", + "integrity": "sha512-1oo0qYsLrdER2jKA50krWOfppk6+zUwYXkoJ5pcRESF2wZFHT1DbuLF7j9ULJNgWIc4OiL1FF6PsGbX4UVO0jg==" }, "favicon/apple-touch-startup-image-750x1334.png": { "src": "favicon/apple-touch-startup-image-750x1334.png", - "integrity": "sha512-fXVcGmV8nj/H4wZRG24ZgUOPO3qjMNMtMPGArIjsdtFC7MugmT2oZlmlZJTN1v6JIOoQPfDEmRBC2OY+83aOFg==" + "integrity": "sha512-XJxhIzy72gE1Cpgf2LYQRRBwm6mwmuXZ0dCaJMebL+dhBCotd9kANbQhVOZXwhajwpxVZrwDnoQFBW551wmXyw==" + }, + "favicon/favicon.ico": { + "src": "favicon/favicon.ico", + "integrity": "sha512-oyLtFbxhoEnH/aFDXDWkC+S1LT5M7VHeH+f+FOLsy8JzsswzGR0VkLu/BFvzyVQTzexmfNjP4ZFm6QJYW1/7hw==" }, "favicon/apple-touch-startup-image-1334x750.png": { "src": "favicon/apple-touch-startup-image-1334x750.png", - "integrity": "sha512-8XSFf8v/KZW7sETjasY2xo7QOjF4rIAyKVlMg0ln3f6ltia/PgMmT2uyZtpfEmVjxhKzCE5sBprWWQMPgCnB5A==" + "integrity": "sha512-z9MTHnbgUAFNt+UEklBBTTdRhvOEDgOr0/ZRFxKibO3qN3l9oRLtWFkgbl/3eNhUpfq57KYsc+IU4/w1ok2jQw==" }, "favicon/apple-touch-startup-image-640x1136.png": { "src": "favicon/apple-touch-startup-image-640x1136.png", - "integrity": "sha512-XXoL6TF7XiLsGSozR/i/rHSLxq4+EYSuJy1yVXkuYD1Z5pKLE79mEixZtIAlFAUH4vp5/jDnqUeLZEF0WKj3Fg==" - }, - "favicon/apple-touch-icon-1024x1024.png": { - "src": "favicon/apple-touch-icon-1024x1024.png", - "integrity": "sha512-24xfiS1TIVCTRTPPBBFqdDquj1YjC5Uv4/27/X6rXavl3EFm8jvyKHJoNNBZnADuPDnNUp3fZ3w8YjFjh/72eg==" + "integrity": "sha512-uq4us0Q1DP1lHqmRSDXrU/gorQahSLk+oKQ7TgczYMNLhKnhi+Y2hHk8FqXvB2B3dJrgyepd0XD2ZMiZudq2bA==" }, "favicon/apple-touch-startup-image-1136x640.png": { "src": "favicon/apple-touch-startup-image-1136x640.png", - "integrity": "sha512-sCGiDX6KSnVLTN3SxgxU3idna/C4kSpxEg1e0LDd5Va9GKGU9Pwxsxbfztovdoa4dCzUQor7bNkP9AV31ZUhHw==" + "integrity": "sha512-Ix/GZO3qGzFYVpIlNa7jYkVyq7n1kAaQZx2Pobw+fqGGE1GqElPKCdUwCBCmxC9OM3qrdA9qB9AHg8IdOqNb/w==" + }, + "katex.css": { + "src": "katex-59efccf3.min.css", + "integrity": "sha512-LoA9jYWADilujFvJrka88siGszaREbfUaqz+lfLV+JJvNCxFCliO5Xxao82BEGTAgFPaWsDRe08PU9CrASv0HA==" }, "favicon/android-chrome-512x512.png": { "src": "favicon/android-chrome-512x512.png", - "integrity": "sha512-4LwQNKmVInikOHD2/rQlGO+YsQ20ty8OPlvY1ZkCTW6z79PzYu7sxBKChoRWZz29Qu+5pswP4gcnlFJM8h16Ig==" - }, - "fonts/KaTeX_AMS-Regular.woff": { - "src": "fonts/KaTeX_AMS-Regular.woff", - "integrity": "sha512-9OTmXDiUyTZC10JExwbsf9iq5LBx+9l7D9C4/6i+l0df+q4VmoRuBkqtOGsQJq6Ak3lnikurNrXbfpooemNRWw==" - }, - "favicon/favicon.ico": { - "src": "favicon/favicon.ico", - "integrity": "sha512-eiPeWA9BpWCHB8RTkHgjSniPpdfHwX28K4PwZRbsFvw/iSg643dh0kzSxoP9PM7TP7HOTtsTjhjkivaLucn8fg==" - }, - "fonts/KaTeX_Main-Regular.woff": { - "src": "fonts/KaTeX_Main-Regular.woff", - "integrity": "sha512-e/R6E/kxpe/ZJOoelEE/Up1luI+TGgnZk7sqD5WEgZni3mT7SuJIXeg+Tds6aQVW3EU5OdrzkQgy7SKvgmlwNw==" + "integrity": "sha512-+zvj9hEahF0mXa6M9DCcmUfrRL2A9DRtRBbIZuD+adAX+KrGEKq/86X6v1zpxq3C34TXFtSroQ51Us0H0sZO9Q==" }, "favicon/android-chrome-384x384.png": { "src": "favicon/android-chrome-384x384.png", - "integrity": "sha512-z6jq3E8UfsKnmAvAqe3f/6zU3G4J64Si2leW1zd+aOXeEmOit/TLX+95PP+nt8RccwNZLSdcvxSSbnO3QOvgiA==" - }, - "fonts/KaTeX_Main-Bold.woff": { - "src": "fonts/KaTeX_Main-Bold.woff", - "integrity": "sha512-+UGGXn4fqiTI5j9vbHK7pGg1ArRyP1KjM10tTyRjdEXUXl8VYS8VFuKcZm7TYzSAUbKCyMROpMarzCPNdhwUYA==" - }, - "favicon/firefox_app_512x512.png": { - "src": "favicon/firefox_app_512x512.png", - "integrity": "sha512-t7wJcQ8LAHmPf6wFzun/zUzG9Ul3VDyzIibk26esgRzt9YAxXQ4QURKjKbdSX+Chozn+hHgbGdwGHUhZCDeIkw==" - }, - "fonts/KaTeX_AMS-Regular.woff2": { - "src": "fonts/KaTeX_AMS-Regular.woff2", - "integrity": "sha512-gAE8LJexY6Fb4a8zluSx/+2E4uy09m2cU4S2aUbdJVMhYine4XXka/ehaMYIPso+KvEjy22Nu9LicCgefbF/gg==" - }, - "fonts/KaTeX_Main-Regular.woff2": { - "src": "fonts/KaTeX_Main-Regular.woff2", - "integrity": "sha512-G/qfHSw59EYNIAQD8uKjJ9K5ZLpOANYUlemDOCbMgEFEt1NoYHBPdBaUk12AWMo1BYb5fMsxnlfRRyMQD0iGIA==" - }, - "fonts/KaTeX_Main-Bold.woff2": { - "src": "fonts/KaTeX_Main-Bold.woff2", - "integrity": "sha512-H+N2wqGFzd+GKbPWL2b/P2EMC2x9xHwWWkv2qsb56vSMQZA+sxRpebHebFddNq3kSXdlE7bhofyupEO+H7oPlg==" + "integrity": "sha512-MN160lMatZUhdpfOjITGN2NRViQENPh59k2sMVyzelDawyWrm9CrK8U/9u642UL0kEhsBc+stqMxsLIPs/0IUA==" }, "favicon/mstile-310x310.png": { "src": "favicon/mstile-310x310.png", - "integrity": "sha512-QMpRgeWeAmOnY+5kV7ko2T90q5Ssf/BdkDlils8RA/os/00+s85+LqCv75LA6x0mURQBRslXAYTvlExXQc8nnQ==" - }, - "katex.css": { - "src": "katex-9967707b.min.css", - "integrity": "sha512-aUUop6VDmegZLh49fW0s65OA1y78h1BGsAxX0uv08tKxMke4WwBw7jkJGSZKBqrCE78geAqsnAl60tSINIgnqQ==" - }, - "fonts/KaTeX_Main-Italic.woff": { - "src": "fonts/KaTeX_Main-Italic.woff", - "integrity": "sha512-OjmWMTSIDlTf1ZGhOQiZsZAQ1cySo4EizhqshTvbuqZkgbUtV0291hC/QN+o4+VFc7OBxaKWYaJgjFRszpQnuA==" - }, - "fonts/KaTeX_Main-BoldItalic.woff": { - "src": "fonts/KaTeX_Main-BoldItalic.woff", - "integrity": "sha512-AXQBZ7CFEPmUhTEmD290away1CMsXG+6B1M2c0kKewQFg5ynwIe/FryXq4dNvcTh6Cjt4PqMMss8oi95k0itSw==" - }, - "fonts/KaTeX_Math-Italic.woff": { - "src": "fonts/KaTeX_Math-Italic.woff", - "integrity": "sha512-TUy17s9hPgsK0he3aJxEtpu4tdrXIgAwSR0wJnkr4b0BNKSEAap1orh7MA2QgT/KOV5ob6ZOWO7HqbwwQ9GVcg==" - }, - "fonts/KaTeX_Math-BoldItalic.woff": { - "src": "fonts/KaTeX_Math-BoldItalic.woff", - "integrity": "sha512-vOUuWrtWrswqo6byaXpdKXUyJVAQjZdovxjXMqt2d6077hOXP4buD9+CEGzgiJdFOLXgVyt663Qg24V6tq7q0g==" + "integrity": "sha512-yznL6hsezsoaIlzrRBhdlvfqaotZ4fDs2O6yFs4ksJ38llkYLEQOK0dR8vRZj7IrBA9cPBpFnF8a7zren1/dsw==" }, "favicon/android-chrome-256x256.png": { "src": "favicon/android-chrome-256x256.png", - "integrity": "sha512-hrtqFFkYWcGSiynPdzkSpODgNSTLfpzDvmYou56Qzi8t3HhZ3jyMDE9g0JcWJ1I6SbaWSfZdkAHXDj2v56x1Og==" - }, - "fonts/KaTeX_Main-Italic.woff2": { - "src": "fonts/KaTeX_Main-Italic.woff2", - "integrity": "sha512-SNdgxBdi/h31Ew67zOq7HpN4HMSa4vcCObb4qEywoA1tsMO8V+FQjZMrzVggok/ZIOK54mYOZBwNHPxiJLwhlw==" - }, - "fonts/KaTeX_Main-BoldItalic.woff2": { - "src": "fonts/KaTeX_Main-BoldItalic.woff2", - "integrity": "sha512-R8cMx8fydyMLYJCaCwtZOVO5dDNFXr7+HQ4k3anhEEwt1D/apo8SHVx6P9z+0b1WiCB3HayQkQmWWgW39rKstQ==" - }, - "fonts/KaTeX_Math-Italic.woff2": { - "src": "fonts/KaTeX_Math-Italic.woff2", - "integrity": "sha512-LRyb4qX7MDVXzDJUxajFm8w3ycI/0r+z6F4qY8c/YbZUYFx246I2HiNeQfpsTCa6nsCtrF4Uah6G0rzJhZR+uA==" - }, - "fonts/KaTeX_Math-BoldItalic.woff2": { - "src": "fonts/KaTeX_Math-BoldItalic.woff2", - "integrity": "sha512-sR3fQuYKVLlf6OGCMP7Fk/S8itLYLDZY/yN9YADDDPEbY0Ii3XaOAR0ytOTUgL8nehwjBdaCw+iMXMwT0rtKqQ==" - }, - "fonts/Metropolis.woff": { - "src": "fonts/Metropolis.woff", - "integrity": "sha512-fqZj5Y6hMExrGIb+OuLPY4hnQ+/ILiPON6MpAc77iKhzTWNn7KvSdfwS2NY5hmAYGfggxl55cYRUgT6F/W/RjQ==" - }, - "fonts/KaTeX_Typewriter-Regular.woff": { - "src": "fonts/KaTeX_Typewriter-Regular.woff", - "integrity": "sha512-B1qvhsqVeoK6poFsOdVkSoAG3RcVLila6oTE8GeRxtrRsGTF2eWKwdY3C6Td1Cijbh6UvjkunBI/2pWW6mCaXg==" - }, - "fonts/KaTeX_SansSerif-Bold.woff": { - "src": "fonts/KaTeX_SansSerif-Bold.woff", - "integrity": "sha512-x8XDvU1FWtV3VSVxwu9zpioBXeiCPU5ePRJintoxp1HuPg3LBF0XC0X9fOnObO6VHEtQtKwzKfevNLhrQWzi9w==" - }, - "fonts/KaTeX_SansSerif-Italic.woff": { - "src": "fonts/KaTeX_SansSerif-Italic.woff", - "integrity": "sha512-WNH/1HTzqy+zZJp9UuT4yMSK/ynD0f2Wd4aY3w0mKUezWJtXn9uUaa3tAdw5rE7finAD2STy24CzwVku+lc5xg==" - }, - "fonts/KaTeX_Typewriter-Regular.woff2": { - "src": "fonts/KaTeX_Typewriter-Regular.woff2", - "integrity": "sha512-S0dhh+bWsw9RVuG+u7wuf+MKUhB6FozZMooTc172VAUU+g1jjXAki8d+/7ecphrmo2/4uumB2bbMdWbvU5u/oA==" - }, - "fonts/KaTeX_Fraktur-Bold.woff": { - "src": "fonts/KaTeX_Fraktur-Bold.woff", - "integrity": "sha512-bbDj1QAzneCTF9//oni1rIQ3wKjtMX2kGbSrYaVNJOCOPOHWD+Mn8ZCACQAKzsVnWX9IB8X1Uwazyjz95kmPAg==" - }, - "fonts/KaTeX_Fraktur-Regular.woff": { - "src": "fonts/KaTeX_Fraktur-Regular.woff", - "integrity": "sha512-bphDtaXYbimBkkS8AIyw0aBbDWoMVkeEIwAnz+Ra1LrcrZxPCaqiV615P3g8zRvWCUifq1fNBAqvHYLqkhd1TA==" + "integrity": "sha512-EoDoPR6+AyBjbQe+6nQuk8ztSv6IyE5r5+ALh8HWjvAIKfoB9gfMuuKlo6PJVFxWyEDgJLpjhmqPMNwrBjK/og==" }, "favicon/android-chrome-192x192.png": { "src": "favicon/android-chrome-192x192.png", - "integrity": "sha512-Tb4H9uC/7OYYBQxRJMO1SIDOvlBS6jHMENnsoKROAUCCjbsptwwMTR0xisgwkJdDkgEH88s9yLj/sy55OJqwtA==" - }, - "fonts/KaTeX_SansSerif-Regular.woff": { - "src": "fonts/KaTeX_SansSerif-Regular.woff", - "integrity": "sha512-O4mHHzWemAibL1YVBTG0lPZWRdcNQ/Qbn2/SvQ5gz9CREHr3pWQgrLI7VuqScudj74azd1v0S4YUiNuoFjwfoA==" + "integrity": "sha512-jURssFPJfNTqdQQM6YGPRIejKPiCmHdydbOkKux0RVGtQdIM/JqpTOIxYtJoWxIATm4yUuoowB8ZLR6irsiTsw==" }, - "fonts/KaTeX_SansSerif-Bold.woff2": { - "src": "fonts/KaTeX_SansSerif-Bold.woff2", - "integrity": "sha512-VWDeiG3/j21h8nr6IlK3IcD9ST9gTGHTAaDC0hFMIqCqWztrzO6H7bVJ2GWOlp9seqrFCQvkrcoEKULdYBxSEg==" - }, - "fonts/KaTeX_SansSerif-Italic.woff2": { - "src": "fonts/KaTeX_SansSerif-Italic.woff2", - "integrity": "sha512-hZ3bBm8cZVRGJ8lu3C9HAvGbBBWc3a5gK7PZj5BcUPmP0zHxCnUQwAjw5M90j26Nu49DmGHd6BpUC3tGRdgSVw==" + "favicon/apple-touch-icon-167x167.png": { + "src": "favicon/apple-touch-icon-167x167.png", + "integrity": "sha512-r21EOceDocSx20MdajHg68eSKbmmHv/bm+1GwZ7cVQmzqiTOAVNgvG7Q7UD3hWzT6PY+YlbU7d2Smfzwgd+NVg==" }, "favicon/apple-touch-icon-180x180.png": { "src": "favicon/apple-touch-icon-180x180.png", - "integrity": "sha512-G/pMzRUISFGsqqFUi+3GgVi2TXN2PmPbpCiYXo9YSi+Rn1dtPmeGDY5GAz4rRzf6kIAlXThmSKTS/rpbKgObuA==" + "integrity": "sha512-EyF0U+VyXr/sfsXNbJwdTQ5IHTRkiy4dp3dnYqgwy5NTelKNTXoo8yn70jm7h5t83BBrNVIGvXfgTUVGjsXQqw==" }, "favicon/apple-touch-icon-precomposed.png": { "src": "favicon/apple-touch-icon-precomposed.png", - "integrity": "sha512-G/pMzRUISFGsqqFUi+3GgVi2TXN2PmPbpCiYXo9YSi+Rn1dtPmeGDY5GAz4rRzf6kIAlXThmSKTS/rpbKgObuA==" + "integrity": "sha512-EyF0U+VyXr/sfsXNbJwdTQ5IHTRkiy4dp3dnYqgwy5NTelKNTXoo8yn70jm7h5t83BBrNVIGvXfgTUVGjsXQqw==" }, "favicon/apple-touch-icon.png": { "src": "favicon/apple-touch-icon.png", - "integrity": "sha512-G/pMzRUISFGsqqFUi+3GgVi2TXN2PmPbpCiYXo9YSi+Rn1dtPmeGDY5GAz4rRzf6kIAlXThmSKTS/rpbKgObuA==" - }, - "fonts/KaTeX_Fraktur-Bold.woff2": { - "src": "fonts/KaTeX_Fraktur-Bold.woff2", - "integrity": "sha512-y7piX+9FWnsdHdn+ZeIbANy0v3KeCMwb0Ygq8IKnZq9tCtyFyEk9NSmqSc+thy0MSyQUhHYhdPCkjSHijHbJgA==" - }, - "fonts/KaTeX_Fraktur-Regular.woff2": { - "src": "fonts/KaTeX_Fraktur-Regular.woff2", - "integrity": "sha512-scyZ311eKDPdtO9dnq+j8r21ahTaiygTrpRE14e7YxUs/7tjMv61mCA+cpLAz0+lzrDaZoyMuCr8yTvqgsSX5Q==" - }, - "fonts/Metropolis.woff2": { - "src": "fonts/Metropolis.woff2", - "integrity": "sha512-oS5Y/tXC8/vG4f7KiHpDicy0yE4zs1TMps9Mzfk3M8O7/QNeC9Q7ZcsjnncuGo0vQB5RXU8g460XpSUB5Luc4Q==" - }, - "favicon/firefox_app_128x128.png": { - "src": "favicon/firefox_app_128x128.png", - "integrity": "sha512-NV/H3Ya562iH3lsTWu3+nE1RZ+wxKKYEMxkYPCH4JLqaesUQfefWavIN0PUzB4TQ0ONmmp4fwkAtXmMh4hplHQ==" - }, - "fonts/KaTeX_Script-Regular.woff": { - "src": "fonts/KaTeX_Script-Regular.woff", - "integrity": "sha512-GnZ6z38QaaRNmfOLaikoe5x0HgxQ0qhfi5eGO6QzdHTXdGiTWxV+RjYPrvoW/vc9Bk/BdVH9vBcIODhXtwsYZg==" - }, - "fonts/KaTeX_SansSerif-Regular.woff2": { - "src": "fonts/KaTeX_SansSerif-Regular.woff2", - "integrity": "sha512-E9Kz6Ra6gXiS2dEGdOw6t9bDwwqYaLGplO0sYwtsh9aveV9xu/j0kwSkr0VZJ+otqrSPzox6sJKTvzlVjQtQsQ==" - }, - "favicon/apple-touch-icon-167x167.png": { - "src": "favicon/apple-touch-icon-167x167.png", - "integrity": "sha512-03qCnveVmQRddor+JMS5JGMGqdkcbCc+rUuXqQGhB34lanb92p2Ipigqa1FINeyYc64DJRrQkzRkWorEqPom6A==" - }, - "fonts/KaTeX_Script-Regular.woff2": { - "src": "fonts/KaTeX_Script-Regular.woff2", - "integrity": "sha512-/jhfsi53uEpLeJpQXaN1nzNSIuRztZkiF6tZ16/KKJ631DD61mQBX80CEnFaQ6+t7t2cuqTUH2xR/WoY8xvvOw==" + "integrity": "sha512-EyF0U+VyXr/sfsXNbJwdTQ5IHTRkiy4dp3dnYqgwy5NTelKNTXoo8yn70jm7h5t83BBrNVIGvXfgTUVGjsXQqw==" }, "favicon/apple-touch-icon-152x152.png": { "src": "favicon/apple-touch-icon-152x152.png", - "integrity": "sha512-AZqdsbtWe2Kccqa1Q8gE/dUCTGo2ZlkdG0rGiamZ3XdynYBL5GnEguQDJjiMWnkblEmlQ8CWE5pxoOQ1TVnQqA==" - }, - "favicon/mstile-150x150.png": { - "src": "favicon/mstile-150x150.png", - "integrity": "sha512-JJCSnHo3cpid9GAXaJz3/PMXcjlWzVYgmKlUwTC7+NJ4vUXdQ6bjHtomGhZMcHOHKy6bT6bwxBip3ngVoAlzNw==" + "integrity": "sha512-lRnGXbXzsVrCxAbIg+I40XjDAsNROy/6BPlPC/+2F9v3p+3g9vcy0F8HA/DlVD1I52HYfNH2p1ux1DbZArdGRQ==" }, "favicon/apple-touch-icon-144x144.png": { "src": "favicon/apple-touch-icon-144x144.png", - "integrity": "sha512-sxApsYMBq0EyzbVYkxKtKTau+noTtKH65s9UEm5LVbeFjMlR5XDTxsEbYNesz/p/DHEg/oeNXAOG1QvCdV+8yw==" + "integrity": "sha512-l79kJAsLq1OgOOq2n2BW21kYMTNd51vkjJwc7q1GYIswhF88t1hoHFQSfVDjIeYChjWKGwxBvHwEwIvjDBW7eQ==" }, "favicon/android-chrome-144x144.png": { "src": "favicon/android-chrome-144x144.png", - "integrity": "sha512-GHmf/LdyneSuxyqoiRP4en4ZDfyU/vJOd5mLK1cW95Hk5Pi+3rvk/R3Cqtkdd4E6tyIwZGHNh+WHL+D6eoOxiA==" + "integrity": "sha512-bT2lgHn/yXT3P6/li3MKnzS7wg9O8gNzZiRn0Wb4/mRGCHnNcWgtbKBQUf3OKDgoX3RLux+RtERWl5Ii4ByGwA==" }, "favicon/mstile-144x144.png": { "src": "favicon/mstile-144x144.png", - "integrity": "sha512-GHmf/LdyneSuxyqoiRP4en4ZDfyU/vJOd5mLK1cW95Hk5Pi+3rvk/R3Cqtkdd4E6tyIwZGHNh+WHL+D6eoOxiA==" - }, - "fonts/KaTeX_Caligraphic-Bold.woff": { - "src": "fonts/KaTeX_Caligraphic-Bold.woff", - "integrity": "sha512-dfUme9QtfyLoXukghFSnTuTlLsQ/8O6YC0QnfbcJermS4BcnS4yWkZT1MEkDPL7/OUnVHcBSuLlvPbQQviJLGw==" - }, - "fonts/KaTeX_Caligraphic-Regular.woff": { - "src": "fonts/KaTeX_Caligraphic-Regular.woff", - "integrity": "sha512-31Lt1ryrvtQyFxwrABw2xZiojGGAxgDf1ojQFVy11mzmlJfn38QVSwxISnudZVBrslsHyahoDuZK6wBrayJ/LA==" - }, - "favicon/apple-touch-icon-120x120.png": { - "src": "favicon/apple-touch-icon-120x120.png", - "integrity": "sha512-SxZdzj6QHtW/aA1qzqxFt+7ukVK1bzTsileyHR8xhVyk7U10DhLH+lkX7/04jctPFQ16p1/aW1XYfus0Az7mxw==" + "integrity": "sha512-bT2lgHn/yXT3P6/li3MKnzS7wg9O8gNzZiRn0Wb4/mRGCHnNcWgtbKBQUf3OKDgoX3RLux+RtERWl5Ii4ByGwA==" }, "favicon/mstile-310x150.png": { "src": "favicon/mstile-310x150.png", - "integrity": "sha512-iby/HgTBJo85KRrZdnhz7cb7ilVeD5sFCeKcCoTf/HAcVqJACSWyRi8zjYVp8QrtjCL2yi9yZ0sUsKfzsKIJdQ==" + "integrity": "sha512-r+sceTi0g2LBKAWM7DHlg3oZwo4cWy+fKjlCydQD3EonqCFDzYXiRDbry3YErC9WNBBliP2bM4xMTrTZgm+Kwg==" }, - "fonts/KaTeX_Caligraphic-Bold.woff2": { - "src": "fonts/KaTeX_Caligraphic-Bold.woff2", - "integrity": "sha512-Ljf53JaOUtVkASUsf8k9tpv6UeNL81MK9LR5Zco6qeIZ7l7oL3heh4SgT5mljccYcPeQ3+qB0JG8GLOiyXcNAg==" - }, - "fonts/KaTeX_Caligraphic-Regular.woff2": { - "src": "fonts/KaTeX_Caligraphic-Regular.woff2", - "integrity": "sha512-lxXHdk259ffCje8TkPwi9v0jtJzlnd2O4FKYgzmdPOGHFdEzdM+7FiAumS0ClHPuqU90I2hTSmpDcpmIT/P8yQ==" + "favicon/mstile-150x150.png": { + "src": "favicon/mstile-150x150.png", + "integrity": "sha512-GPqhaPXp69HpgcB2Ah/xazMg3W3KmL8XMOH8nhvKUkU3o3JSkw1DIaEzZgtL7upFEkXluCdyRAt/6Dm16fh+DQ==" }, "favicon/apple-touch-icon-114x114.png": { "src": "favicon/apple-touch-icon-114x114.png", - "integrity": "sha512-LGiIXYDx+ERXCQDJCktzcb03hmvkGZdRBh2X9SX9esa/A9GXQyWIwN4+6KxKD+Wtvy4YxzaJK98HZs3538CDGg==" - }, - "fonts/KaTeX_Size1-Regular.woff": { - "src": "fonts/KaTeX_Size1-Regular.woff", - "integrity": "sha512-XkOjZvm8Ok63zQ6QfZMMFYV0/WG59qxy8+n8Iu6Vqzbo9S+HhvsUpoQDEKLOS/BlLmvWQxjKoDJRdwuht5XP7A==" - }, - "fonts/KaTeX_Size2-Regular.woff": { - "src": "fonts/KaTeX_Size2-Regular.woff", - "integrity": "sha512-8lcSsq+0OUHQ1txGOO+hbXlRT9HjF0XmcVXU1trgKtDJx+fq2ejI00wJX/Jd+qw7e70BsOIuhvhY/9Og5wGxiw==" + "integrity": "sha512-H+TUSPPwtVHdJabqOhKdPxWjB9nBs+PMgjvU8+k2ODvo3LKfBoJGm1GZ3yxYcI5NN4a7lp/CEl3dpl7ip3WGrg==" }, - "fonts/GeekdocIcons.woff": { - "src": "fonts/GeekdocIcons.woff", - "integrity": "sha512-6shzg8V4fFarsejo8qlvY3M/n2woxXTl/kRF9MvK+8u2H06mpbKzYyg2KuDIZtFU4cu5qke299EuaCaqvG83qg==" + "favicon/apple-touch-icon-120x120.png": { + "src": "favicon/apple-touch-icon-120x120.png", + "integrity": "sha512-lmal7rWGjyN5/sPczKKS2f9PArcANPIpGSIBXeihN9qOXty8XmZXIRdxlgWD+57jqTnJVTAeJSkK0dXFaQnG2Q==" }, - "fonts/KaTeX_Size4-Regular.woff": { - "src": "fonts/KaTeX_Size4-Regular.woff", - "integrity": "sha512-Yn3rCn0/wh7IJxqTkxIMQmE2R1WuooBx1NXlsrmmodUYljKoXDiY5V/ENfqav4ZaVoHfuahfqQyvwQ8GyMgtLQ==" + "favicon/manifest.webmanifest": { + "src": "favicon/manifest.webmanifest", + "integrity": "sha512-jWI8l1WzeZTVACRS28IeRRCxVue3FSmpky9ou90cG6sc7e9kmJtfQ9NfoFMYyOZ0xIqiA6N2FFD1e/Sx7VXK4g==" }, "favicon/android-chrome-96x96.png": { "src": "favicon/android-chrome-96x96.png", - "integrity": "sha512-Yf4VS4jjOTKPur035TkNhAybS3p2x+jrNli0lyHZJRbfNy2Csi0a7ilwhsVCUl4LFp/JxwFmXFxprI7WwW8o4Q==" - }, - "fonts/KaTeX_Size1-Regular.woff2": { - "src": "fonts/KaTeX_Size1-Regular.woff2", - "integrity": "sha512-LWpGCcgzMsmdUrmsBIN5OaYPFpbpYpEn+YmrnLfiCbdq8s7aNTtNqdyUxmExvdZ2Vo2Bz87pfzjvN+xtL4+JTg==" - }, - "fonts/KaTeX_Size2-Regular.woff2": { - "src": "fonts/KaTeX_Size2-Regular.woff2", - "integrity": "sha512-BHXH2ZEkl2rojultow0zLPFK6z4CAw4f7zYtMCTNhaXYnmELwHufinI8V3Mc45cLgD+IPwsA9hoinugMQimuwQ==" - }, - "fonts/GeekdocIcons.woff2": { - "src": "fonts/GeekdocIcons.woff2", - "integrity": "sha512-i8RlKFwGX6EDKhZ3J+KGV5QUzbv7Lwwsk5bAGUvVSa4FREWr59xi4CsuCUIaPwSNalhxC32H0EPRX2S8yK3bJw==" - }, - "fonts/KaTeX_Size4-Regular.woff2": { - "src": "fonts/KaTeX_Size4-Regular.woff2", - "integrity": "sha512-F6rlkx62QlxNW+vGsxhQWgaktNw+Gzb6fmNF9fEzx5O/FWE+6L7711Q5jKjzMQVeegEdiSeSqvncYFE3j81Dzw==" + "integrity": "sha512-rY/ZP3MD9/rW30HxMcFIqMWmFE8NDcxtqZZV2TjfHedwuE6DQKtx+KnvrLZdB6yZ7AhdsmsGsqn/a+WukRV6rw==" }, - "favicon/firefox_app_60x60.png": { - "src": "favicon/firefox_app_60x60.png", - "integrity": "sha512-YrL6darERXRvnGbTP7MWZFaIqiXEurdbwsg6HeWl+0wlZPC6Wy9DHUcTfjtixv+1+DAVal8YLoIqXTAyYcN+lw==" - }, - "fonts/KaTeX_Size3-Regular.woff": { - "src": "fonts/KaTeX_Size3-Regular.woff", - "integrity": "sha512-Eo35y+ZReFpmRgZv0N2dKAc5ggcXGE3wnrCf28qnu5G1+Ikvew0IFkdCYN1I6FnHsPDTnQzjJZU7+F7oBYvHKQ==" + "mobile.scss": { + "src": "mobile-79ddc617.min.css", + "integrity": "sha512-dzw2wMOouDwhSgstQKLbXD/vIqS48Ttc2IV6DeG7yam9yvKUuChJVaworzL8s2UoGMX4x2jEm50PjFJE4R4QWw==" }, - "favicon/android-chrome-72x72.png": { - "src": "favicon/android-chrome-72x72.png", - "integrity": "sha512-PFMzr2iXImbdQDiZVNugaYzoQAUvLCsNrnm0pV0zLxIuczytBfu1nGKjfJZwyOfMzKQpQfYi4z0cdgit9bJ5IQ==" + "favicon/apple-touch-icon-72x72.png": { + "src": "favicon/apple-touch-icon-72x72.png", + "integrity": "sha512-ZatvjdL1snYdxe3iXsOU0ltj2Ci3v+zK/GOZ6sb64zDrUSn7VO7imIyGcMUJEX9GJ2Blg5o5R7JFQaCS+ejuJg==" }, "favicon/apple-touch-icon-76x76.png": { "src": "favicon/apple-touch-icon-76x76.png", - "integrity": "sha512-klHJxEbcTgx7V1TBM/gByA6vzXK5MUkaiQ1gTybEp6g0sMzpIdO9XTIrsDeelKGI1aHtSBEWPLqF1rYp5T1Oiw==" + "integrity": "sha512-WwGHnql4UjcNS6UwgjUR7UVaXeLtL32VfbFmNloqRECNEgrNe7DCl4ojpNFB/VX2LjP2dzZl7D0GKdzM1PF+6Q==" }, - "favicon/apple-touch-icon-72x72.png": { - "src": "favicon/apple-touch-icon-72x72.png", - "integrity": "sha512-UHXFta5GyLLQ5IoVQBJGP4yzOVwkRG6m6YTrhw/ABwjgsKX4P3u6h2VnbKpGyRgf5hdORwUFyYrJBgQpIzpNWQ==" + "favicon/android-chrome-72x72.png": { + "src": "favicon/android-chrome-72x72.png", + "integrity": "sha512-5+665FIGx0WNd2RMotKVPKd6Hr5B91p3PEJDk5tphr8YT6TjMRlOAO3S1JiyRgYX1Ad2zvBy16O9m2xe6Oq+/g==" }, "favicon/mstile-70x70.png": { "src": "favicon/mstile-70x70.png", - "integrity": "sha512-Wj4a4NiSy1axSDENK/8G13PQPQuaEaFPxOBCj+iVmZ0ifk2UMPbtZDo2fdpCTtwS7BWiHCyv97Kvg4sqAZbRjA==" - }, - "fonts/KaTeX_Size3-Regular.woff2": { - "src": "fonts/KaTeX_Size3-Regular.woff2", - "integrity": "sha512-avq+5YU1Cd9KtJ0U6hujFkh4fMNVZC59Y/HPlqUXkxeUlWca4Fmqn1YA8WK1188GG59qRHBtoZzvRgC9k9fGZA==" + "integrity": "sha512-uk2TelCQgggqSrlXZqNI1jvoS9c1whC2No7MVAwo5A436F9YnrN8UcRQR8FIqXcf1QYdDqgu7T/PsfdKJGpXqA==" }, - "favicon/favicon-48x48.png": { - "src": "favicon/favicon-48x48.png", - "integrity": "sha512-rs5vrXU7NuGuRCv1RimyERFr1DzyQBviAoYmJxD8uHjl0y57SAleg14A0piuXkRUDL1FE0ZyVA/INPz+8GQjpg==" + "favicon/apple-touch-icon-57x57.png": { + "src": "favicon/apple-touch-icon-57x57.png", + "integrity": "sha512-i9gj+VyNSIRC+MzON9tQLFnJeILsNOSBM+pQ7Xvlvme9T+tddMojY1bdqsy7EbYt0wxmjAg+4OaTPWCP6mq29A==" }, "favicon/apple-touch-icon-60x60.png": { "src": "favicon/apple-touch-icon-60x60.png", - "integrity": "sha512-YTKtyy5p2t+jz9cFS1c9kFm0LMH95s37ZRuL5AS0Lhpkf8B+xSokR/v+3xxYskT3QRSx5vcyHai8ia44X6xT5Q==" + "integrity": "sha512-s0boJ/PY8f+qT/WS73RM5n9UNxtUvjemVOvKpC2Yz8e1iT7zxhQUwg4FRQ3kWMe0jGjA33unYfH0h7HPyXhfgQ==" }, - "favicon/apple-touch-icon-57x57.png": { - "src": "favicon/apple-touch-icon-57x57.png", - "integrity": "sha512-2yW78pw4eDZ7hEUoWvNaEEeXOf30rUaKXoZwSvrilX8xBvqij/opEMPF0OHHU2lQziDIGGN9YsUnASQbhd95bw==" + "favicon/favicon-48x48.png": { + "src": "favicon/favicon-48x48.png", + "integrity": "sha512-lJ6dZkyrr8SJezHQg048oTdSW2Y7hsBdYITYStFpbiBUNMoVcrqzLl9I6pkuwfitMevjJNr5VZA0EDvy+4fhFQ==" }, "favicon/android-chrome-48x48.png": { "src": "favicon/android-chrome-48x48.png", - "integrity": "sha512-UTXsN/aHnuTWAyYmp+/Ov0H1ML3HnIfUvYuwPyeWTwRs/8bZETHUYsj4scx48YkAJ+fhRnobXYqwr0swY7cXeQ==" + "integrity": "sha512-4TLfus/Gh7ss8fkmuvguqSV7onXq8kkXwqqRq2nabo9L1T1N4055IGHV2ByPF6DQPs6iJO0848eF4LJHs12Fxg==" }, "favicon/favicon-32x32.png": { "src": "favicon/favicon-32x32.png", - "integrity": "sha512-cT9VQkceXZYw+3yDSljXGTmfHdp6Gh+ncc7mdtKoB3AK4a6MgMR1YTeGpC0IOm5EmMQoPICXwvMPWskD9YSUAA==" + "integrity": "sha512-5elFUf6p+aWoJI3WIS3dhk3MIAqMMM1XFsVZpzG63sITcr1I8iAfjsCIYTJ3fTvSSoFlFRKZ9djMVSNDEK6DqA==" }, "favicon/android-chrome-36x36.png": { "src": "favicon/android-chrome-36x36.png", - "integrity": "sha512-uIOaCXbCeY2tIMUPros0wfBdDIh842crzDQ/4NjTeEqzFhwrK3HlTsdFXYNcqg9OOuO+aATZKwIGYbgaA8vQbA==" - }, - "mobile.scss": { - "src": "mobile-c0e18b0e.min.css", - "integrity": "sha512-1cYsUptHmhBa/5ptM01j/+zyvdld84SCFnsrH97EzpxXeY6guXfc74qYzCuS0BiubMJOoDK5IYrivr2rIKezFA==" + "integrity": "sha512-+cyRuV3w4FEq8DVZRGZ9CTiVja2RtOd9PmAIRciFDEpBX3KhdWS8sbLVl7FQ/yX5IkB8xmPla4VJjcgpcftO8w==" }, - "favicon/manifest.json": { - "src": "favicon/manifest.json", - "integrity": "sha512-UncSB3MQSXZlaaxiclpQvvZDDYew4CITJ7JTlLcb8kZpyB3YgbqdHBIechH3HIQ1uJsYhgQyItxG3VMxOLfzKw==" + "print.scss": { + "src": "print-735ccc12.min.css", + "integrity": "sha512-c28KLNtBnKDW1+/bNWFhwuGBLw9octTXA2wnuaS2qlvpNFL0DytCapui9VM4YYkZg6e9TVp5LyuRQc2lTougDw==" }, "favicon/favicon-16x16.png": { "src": "favicon/favicon-16x16.png", - "integrity": "sha512-lQ+H0RYy3ZlksL5zUaV2WcH2PQdG6imd5hr1KfQOK4o1LXm7JAHvyjOSN3E+HC+AN1pCuoaITo6UI3SpW+CHNA==" - }, - "print.scss": { - "src": "print-19966b38.min.css", - "integrity": "sha512-xpNQeJp9e4SbqEv+pFoGrOehV+RABxosG+toy6+HJ6SGFLxJNgG4+/RwPYdg3BxBvRXfkTmwf+iArAT3/a3+3g==" + "integrity": "sha512-w2lU/rHj2Yf/yb5QMLW9CMSVv8jCr2kBqvqekSINDI7K7oga1RSeCPEtgcSy9n6zQzdFOmswybhPtNJhPcD9TA==" }, "favicon/browserconfig.xml": { "src": "favicon/browserconfig.xml", - "integrity": "sha512-RDr7E4dJmkJdQMyNa4dtxx3iYnrSnFHlifwV1LriUChccTz+aB0gNe0CRL94GXHGd1DiUU+QgEXXNC2Mupn9aw==" - }, - "favicon/manifest.webapp": { - "src": "favicon/manifest.webapp", - "integrity": "sha512-XPr/eyO6YOVNkn3FS0wAMxe2FPIQmzn5YvV0E2kJ+feOQG4pzZVL09aa35gSUjLR18BVenKZTTtJy4Yh+reRTQ==" + "integrity": "sha512-cUHMy43WEDyWiiDTIcOab69HpATbZfoMFHJTYFx3SiU+vXLMHqo3w3mgQnrvdfs42gp37T+bw05l1qLFxlGwoA==" }, "custom.css": { "src": "custom.css", "integrity": "sha512-1kALo+zc1L2u1rvyxPIew+ZDPWhnIA1Ei2rib3eHHbskQW+EMxfI9Ayyva4aV+YRrHvH0zFxvPSFIuZ3mfsbRA==" - }, - "../VERSION": { - "src": "../VERSION", - "integrity": "sha512-XvmKEkEMPw1R0QWLSlyw+M6neMQWL9wY9CDkuKw5bMFrMDp9WMhKA6c/4smMDSZSxbM8WO0+kJGl+x+7n+OHcw==" } } \ No newline at end of file diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/am.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/am.yaml new file mode 100644 index 000000000..d45f53629 --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/am.yaml @@ -0,0 +1,52 @@ +--- +edit_page: ገጹን ማስተካከያ + +nav_navigation: መሄጃ +nav_tags: መለያዎች +nav_more: ተጨማሪ +nav_top: ወደ ላይ ተመለስ + +form_placeholder_search: ፈልግ + +error_page_title: ጠፋብዎት? አይጨነቁ። +error_message_title: ጠፋብዎት? +error_message_code: አልተገኘም +error_message_text: > + ገጹን ማግኘት አልተቻለም፤ ነገር ግን አይጨነቁ፤ በዚህ ገጽ መመለስ ይችላሉ። + +button_toggle_dark: ብሩህ/ጨለማ መቀያየሪያ +button_nav_open: መሄጃውን ክፈት +button_nav_close: መሄጃውን ዝጋ +button_menu_open: ምርጫዎችን ክፈት +button_menu_close: ምርጫዎችን ዝጋ +button_homepage: ወደ መጀመሪያ ገጽ ተመለስ + +title_anchor_prefix: "ማያያዣ ወደ:" + +posts_read_more: ሙሉውን ያንብቡ +posts_read_time: + one: "ለማንበብ አንድ ደቂቃ" + other: "{{ . }} ደቂቃዎች ለማንበብ" +posts_update_prefix: መጨረሻ የዘመነው +posts_count: + one: "አንድ ጽሑፍ" + other: "{{ . }} ጽሑፎች" +posts_tagged_with: ከ '{{ . }}' ጋር የተዛመዱ ጽሑፎች በሙሉ + +footer_build_with: > + በ Hugo የተገነባ ከ + ጋር +footer_legal_notice: ሕጋዊ መረጃዎች +footer_privacy_policy: ስለ መረጃዎ አያያዝ ያለን አቋም +footer_content_license_prefix: > + ስለ ይዘቱ ባለመብትነት መረጃ + +language_switch_no_tranlation_prefix: "ያልተተረጐመ ገጽ:" + +propertylist_required: ግድ የሚያስፈልግ +propertylist_optional: ግድ ያልሆነ +propertylist_default: በባዶ ፈንታ + +pagination_page_prev: ያለፈው +pagination_page_next: ቀጣይ +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/cs.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/cs.yaml index 7853e4a95..71dd8ed30 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/cs.yaml +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/cs.yaml @@ -47,3 +47,7 @@ language_switch_no_tranlation_prefix: "Stránka není přeložena:" propertylist_required: povinné propertylist_optional: volitené propertylist_default: výchozí + +pagination_page_prev: předchozí +pagination_page_next: další +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/de.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/de.yaml index 83e8537b9..ae3dc99fc 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/de.yaml +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/de.yaml @@ -47,3 +47,7 @@ language_switch_no_tranlation_prefix: "Seite nicht übersetzt:" propertylist_required: erforderlich propertylist_optional: optional propertylist_default: Standardwert + +pagination_page_prev: vorher +pagination_page_next: weiter +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/en.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/en.yaml index 1807dc81d..ff19ea4e8 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/en.yaml +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/en.yaml @@ -47,3 +47,7 @@ language_switch_no_tranlation_prefix: "Page not translated:" propertylist_required: required propertylist_optional: optional propertylist_default: default + +pagination_page_prev: prev +pagination_page_next: next +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/es.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/es.yaml new file mode 100644 index 000000000..8e65cec7b --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/es.yaml @@ -0,0 +1,53 @@ +--- +edit_page: Editar página + +nav_navigation: Navegación +nav_tags: Etiquetas +nav_more: Más +nav_top: Inicio de la página + +form_placeholder_search: Buscar + +error_page_title: Perdido? No te preocupes +error_message_title: Perdido? +error_message_code: Error 404 +error_message_text: > + Al parecer, lo que estás buscando no pudo ser encontrado. No te preocupes, podemos + llevarte de vuelta al inicio. + +button_toggle_dark: Cambiar el modo Oscuro/Claro/Auto +button_nav_open: Abrir la Navegación +button_nav_close: Cerrar la Navegación +button_menu_open: Abrir el Menú Bar +button_menu_close: Cerrar el Menú Bar +button_homepage: Volver al Inicio + +title_anchor_prefix: "Anclado a:" + +posts_read_more: Lee la publicación completa +posts_read_time: + one: "Un minuto para leer" + other: "{{ . }} minutos para leer" +posts_update_prefix: Actualizado en +posts_count: + one: "Una publicación" + other: "{{ . }} publicaciones" +posts_tagged_with: Todas las publicaciones etiquetadas con '{{ . }}' + +footer_build_with: > + Creado con Hugo y + +footer_legal_notice: Aviso Legal +footer_privacy_policy: Política de Privacidad +footer_content_license_prefix: > + Contenido licenciado con + +language_switch_no_tranlation_prefix: "Página no traducida:" + +propertylist_required: requerido +propertylist_optional: opcional +propertylist_default: estándar + +pagination_page_prev: previo +pagination_page_next: siguiente +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/fr.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/fr.yaml new file mode 100644 index 000000000..bbded8579 --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/fr.yaml @@ -0,0 +1,53 @@ +--- +edit_page: Editer la page + +nav_navigation: Navigation +nav_tags: Tags +nav_more: Plus +nav_top: Retour au haut de page + +form_placeholder_search: Chercher + +error_page_title: Perdu? Ne t'inquiète pas +error_message_title: Perdu? +error_message_code: Error 404 +error_message_text: > + On dirait que ce que vous cherchez est introuvable. Ne vous inquiétez pas, nous pouvons + vous ramèner à la page d'accueil. + +button_toggle_dark: Basculer le mode Sombre/Clair/Auto +button_nav_open: Ouvrir la navigation +button_nav_close: Fermer la navigation +button_menu_open: Ouvrir la barre de menus +button_menu_close: Fermer la barre de menus +button_homepage: retour à la page d'accueil + +title_anchor_prefix: "Ancrer à :" + +posts_read_more: Lire l'article complet +posts_read_time: + one: "Une minute pour lire" + other: "{{ . }} minutes à lire" +posts_update_prefix: Mis à jour le +posts_count: + one: "Un billet" + other: "{{ . }} billets" +posts_tagged_with: Tous les articles marqués avec '{{ . }}' + +footer_build_with: > + Construit avec Hugo et + +footer_legal_notice: Mentions légales +footer_privacy_policy: Politique de confidentialité +footer_content_license_prefix: > + Contenu sous licence + +language_switch_no_tranlation_prefix: "Page non traduite:" + +propertylist_required: requis +propertylist_optional: facultatif +propertylist_default: défaut + +pagination_page_prev: précédent +pagination_page_next: suivant +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/it.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/it.yaml index db535dcb6..ce7c40b4e 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/it.yaml +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/it.yaml @@ -47,3 +47,7 @@ language_switch_no_tranlation_prefix: "Pagina non tradotta:" propertylist_required: richiesto propertylist_optional: opzionale propertylist_default: valore predefinito + +pagination_page_prev: precedente +pagination_page_next: prossimo +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/ja.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/ja.yaml index b7ccfc0c9..506e7b4e1 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/ja.yaml +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/ja.yaml @@ -27,7 +27,7 @@ title_anchor_prefix: "アンカー先:" posts_read_more: 全投稿を閲覧 posts_read_time: one: "読むのに 1 分かかります" - other: "読むのに要する時間 {{ . } (分)}" + other: "読むのに要する時間 {{ . }} (分)" posts_update_prefix: 更新時刻 posts_count: one: "一件の投稿" @@ -36,7 +36,7 @@ posts_tagged_with: "'{{ . }}'のタグが付いた記事全部" footer_build_with: > Hugo でビルドしています。 - + footer_legal_notice: 法的な告知事項 footer_privacy_policy: プライバシーポリシー footer_content_license_prefix: > @@ -47,3 +47,7 @@ language_switch_no_tranlation_prefix: "未翻訳のページ:" propertylist_required: 必須 propertylist_optional: 任意 propertylist_default: 既定値 + +pagination_page_prev: 前 +pagination_page_next: 次 +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/nl.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/nl.yaml new file mode 100644 index 000000000..8e24d62a4 --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/nl.yaml @@ -0,0 +1,53 @@ +--- +edit_page: Wijzig pagina + +nav_navigation: Navigatie +nav_tags: Markering +nav_more: Meer +nav_top: Terug naar boven + +form_placeholder_search: Zoek + +error_page_title: Verdwaald? Geen probleem +error_message_title: Verdwaald? +error_message_code: Error 404 +error_message_text: > + Het lijkt er op dat wat je zoekt niet gevonden kan worden. Geen probleem, + we kunnen je terug naar de startpagina brengen. + +button_toggle_dark: Wijzig Donker/Licht/Auto weergave +button_nav_open: Open navigatie +button_nav_close: Sluit navigatie +button_menu_open: Open menubalk +button_menu_close: Sluit menubalk +button_homepage: Terug naar startpagina + +title_anchor_prefix: "Link naar:" + +posts_read_more: Lees volledige bericht +posts_read_time: + one: "Een minuut leestijd" + other: "{{ . }} minuten leestijd" +posts_update_prefix: Bijgewerkt op +posts_count: + one: "Een bericht" + other: "{{ . }} berichten" +posts_tagged_with: Alle berichten gemarkeerd met '{{ . }}' + +footer_build_with: > + Gebouwd met Hugo en + +footer_legal_notice: Juridische mededeling +footer_privacy_policy: Privacybeleid +footer_content_license_prefix: > + Inhoud gelicenseerd onder + +language_switch_no_tranlation_prefix: "Pagina niet vertaald:" + +propertylist_required: verplicht +propertylist_optional: optioneel +propertylist_default: standaard + +pagination_page_prev: vorige +pagination_page_next: volgende +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/oc.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/oc.yaml new file mode 100644 index 000000000..a68685f3e --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/oc.yaml @@ -0,0 +1,53 @@ +--- +edit_page: Modificar la pagina + +nav_navigation: Navegacion +nav_tags: Etiquetas +nav_more: Mai +nav_top: Tornar ennaut + +form_placeholder_search: Cercar + +error_page_title: Perdut ? Cap de problèma +error_message_title: Perdut ? +error_message_code: Error 404 +error_message_text: > + Sembla que cercatz quicòm que se pòt pas trobat. Vos’n fagatz pas vos podèm + tornar a la pagina d’acuèlh. + +button_toggle_dark: Alternar lo mòde escur/clar/auto +button_nav_open: Dobrir la navegacion +button_nav_close: Tampar la navegacion +button_menu_open: Dobrir la barra de menú +button_menu_close: Tampar la barra de menú +button_homepage: Tornar a la pagina d’acuèlh + +title_anchor_prefix: "Ancorar a:" + +posts_read_more: Legir la publicacion complèta +posts_read_time: + one: "Una minuta de lectura" + other: "{{ . }} minutas de lectura" +posts_update_prefix: Actualizada lo +posts_count: + one: "Una publicacion" + other: "{{ . }} publicacions" +posts_tagged_with: Totas las publicacions amb '{{ . }}' + +footer_build_with: > + Construch amb Hugo e + +footer_legal_notice: Mencions legalas +footer_privacy_policy: politica de confidencialitat +footer_content_license_prefix: > + Contengut sota licéncia + +language_switch_no_tranlation_prefix: "Pagina non traducha :" + +propertylist_required: requerit +propertylist_optional: opcional +propertylist_default: per defaut + +pagination_page_prev: prec. +pagination_page_next: seg. +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/zh-cn.yaml b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/zh-cn.yaml index efbb14dc1..e6403acd1 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/i18n/zh-cn.yaml +++ b/doc/src/main/hugo/themes/hugo-geekdoc/i18n/zh-cn.yaml @@ -37,7 +37,6 @@ posts_tagged_with: 所有带有“{{ . }}”标签的帖子。 footer_build_with: > 基于 Hugo 制作 - footer_legal_notice: "法律声明" footer_privacy_policy: "隐私政策" footer_content_license_prefix: > @@ -48,3 +47,7 @@ language_switch_no_tranlation_prefix: "页面未翻译:" propertylist_required: 需要 propertylist_optional: 可选 propertylist_default: 默认值 + +pagination_page_prev: 以前 +pagination_page_next: 下一个 +pagination_page_state: "{{ .PageNumber }}/{{ .TotalPages }}" diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/images/readme.png b/doc/src/main/hugo/themes/hugo-geekdoc/images/readme.png index d9ce58137..10c8ff157 100644 Binary files a/doc/src/main/hugo/themes/hugo-geekdoc/images/readme.png and b/doc/src/main/hugo/themes/hugo-geekdoc/images/readme.png differ diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/images/screenshot.png b/doc/src/main/hugo/themes/hugo-geekdoc/images/screenshot.png index 7ac25799a..af243606d 100644 Binary files a/doc/src/main/hugo/themes/hugo-geekdoc/images/screenshot.png and b/doc/src/main/hugo/themes/hugo-geekdoc/images/screenshot.png differ diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/images/tn.png b/doc/src/main/hugo/themes/hugo-geekdoc/images/tn.png index b3bb1d350..ee6e42ed0 100644 Binary files a/doc/src/main/hugo/themes/hugo-geekdoc/images/tn.png and b/doc/src/main/hugo/themes/hugo-geekdoc/images/tn.png differ diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/404.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/404.html index f8a61bb53..ee7ba2d59 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/404.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/404.html @@ -27,7 +27,7 @@
{{ i18n "error_message_title" }}
{{ i18n "error_message_code" }}
- {{ i18n "error_message_text" .Site.BaseURL | safeHTML }} + {{ i18n "error_message_text" .Site.Home.Permalink | safeHTML }}
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-codeblock-mermaid.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-codeblock-mermaid.html index d3545db27..b5deb66b4 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-codeblock-mermaid.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-codeblock-mermaid.html @@ -1,11 +1,11 @@ {{ if not (.Page.Scratch.Get "mermaid") }} - + {{ .Page.Scratch.Set "mermaid" true }} {{ end }} -
+
   {{- .Inner -}}
 
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-heading.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-heading.html index 3541446cf..3e7a270f3 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-heading.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/_markup/render-heading.html @@ -1,19 +1,25 @@ -{{- $showAnchor := (and (default true .Page.Params.GeekdocAnchor) (default true .Page.Site.Params.GeekdocAnchor)) -}} +{{- $showAnchor := (and (default true .Page.Params.geekdocAnchor) (default true .Page.Site.Params.geekdocAnchor)) -}} {{- if $showAnchor -}} -
- +
+ {{ .Text | safeHTML }} - - - + + +
{{- else -}}
- + {{ .Text | safeHTML }}
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/baseof.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/baseof.html index 2c953f4f2..bd3b9d082 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/baseof.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/baseof.html @@ -2,7 +2,7 @@ {{ partial "head/meta" . }} @@ -26,11 +26,11 @@
- {{ $navEnabled := default true .Page.Params.GeekdocNav }} + {{ $navEnabled := default true .Page.Params.geekdocNav }} {{ partial "site-header" (dict "Root" . "MenuEnabled" $navEnabled) }} @@ -46,9 +46,16 @@ {{ template "main" . }} + {{ $showPrevNext := (default true .Site.Params.geekdocNextPrev) }} + {{ if $showPrevNext }} + {{ end }}
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/list.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/list.html index 9e7a5b845..94172f65d 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/list.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/list.html @@ -3,7 +3,7 @@

{{ partial "utils/title" . }}

{{ partial "utils/content" . }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/single.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/single.html index 9e7a5b845..94172f65d 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/single.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/single.html @@ -3,7 +3,7 @@

{{ partial "utils/title" . }}

{{ partial "utils/content" . }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/taxonomy.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/taxonomy.html index 5b32a6b66..bb97e8ed4 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/taxonomy.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/taxonomy.html @@ -31,6 +31,7 @@

{{ end }} + {{ partial "pagination.html" . }} {{ end }} {{ define "post-tag" }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/terms.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/terms.html index fa9788773..2316ef56d 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/terms.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/_default/terms.html @@ -28,4 +28,5 @@

{{ end }} + {{ partial "pagination.html" . }} {{ end }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/foot.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/foot.html index 99dbffa6d..2a115e562 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/foot.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/foot.html @@ -1,4 +1,4 @@ -{{ if default true .Site.Params.GeekdocSearch }} +{{ if default true .Site.Params.geekdocSearch }} {{- $searchConfigFile := printf "search/%s.config.json" .Language.Lang -}} {{- $searchConfig := resources.Get "search/config.json" | resources.ExecuteAsTemplate $searchConfigFile . | resources.Minify -}} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/head/others.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/head/others.html index a9c9f341e..06f346dda 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/head/others.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/head/others.html @@ -1,3 +1,6 @@ +{{- if default true .Site.Params.geekdocDarkModeToggle }} + +{{- end }} ` .Permalink .Rel .MediaType.Type | safeHTML }} {{- end }} -{{- if (default false $.Site.Params.GeekdocOverwriteHTMLBase) }} - +{{- if (default false $.Site.Params.geekdocOverwriteHTMLBase) }} + {{- end }} {{ printf "" "Made with Geekdoc theme https://github.com/thegeeklab/hugo-geekdoc" | safeHTML }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-nextprev.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-bundle-np.html similarity index 57% rename from doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-nextprev.html rename to doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-bundle-np.html index 0af61ace3..593b649c9 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-nextprev.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-bundle-np.html @@ -6,9 +6,9 @@ {{ $current.Scratch.Set "nextPage" false }} {{ $current.Scratch.Set "prevPage" false }} -{{ template "menu_nextprev" dict "sect" $.Site.Data.menu.main.main "current" $current "site" $site }} +{{ template "menu-bundle-np" dict "sect" $.Site.Data.menu.main.main "current" $current "site" $site }} -{{ define "menu_nextprev" }} +{{ define "menu-bundle-np" }} {{ $current := .current }} {{ $site := .site }} @@ -44,35 +44,32 @@ {{ $sub := default false .sub }} {{ if $sub }} - {{ template "menu_nextprev" dict "sect" $sub "current" ($current.Scratch.Get "current") "site" ($current.Scratch.Get "site") }} + {{ template "menu-bundle-np" dict "sect" $sub "current" ($current.Scratch.Get "current") "site" ($current.Scratch.Get "site") }} {{ end }} {{ end }} {{ end }} -{{ $showPrevNext := (and (default true .Site.Params.GeekdocNextPrev) .Site.Params.GeekdocMenuBundle) }} -{{ if $showPrevNext }} - - {{ with ($current.Scratch.Get "prevPage") }} - - gdoc_arrow_left_alt - {{ .name }} - - {{ end }} - - - {{ with ($current.Scratch.Get "nextPage") }} - - {{ .name }} - gdoc_arrow_right_alt - - {{ end }} - -{{ end }} + + {{ with ($current.Scratch.Get "prevPage") }} + + gdoc_arrow_left_alt + {{ .name }} + + {{ end }} + + + {{ with ($current.Scratch.Get "nextPage") }} + + {{ .name }} + gdoc_arrow_right_alt + + {{ end }} + diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-bundle.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-bundle.html index 32d4e5f3b..d9dcfbbeb 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-bundle.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-bundle.html @@ -25,7 +25,7 @@ {{ $isCurrent := eq $current $this }} {{ $isAncestor := $this.IsAncestor $current }} {{ $id := substr (sha1 $this.Permalink) 0 8 }} - {{ $doCollapse := and (isset . "sub") (or $this.Params.GeekdocCollapseSection (default false .Site.Params.GeekdocCollapseAllSections)) }} + {{ $doCollapse := and (isset . "sub") (or $this.Params.geekdocCollapseSection (default false .Site.Params.geekdocCollapseAllSections)) }} {{ $anchor := default "" .anchor }} {{ if $anchor }} @@ -52,7 +52,7 @@ {{ end }} - diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-filetree-np.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-filetree-np.html new file mode 100644 index 000000000..8c50969e0 --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-filetree-np.html @@ -0,0 +1,107 @@ +{{ $current := . }} +{{ $site := .Site }} +{{ $current.Scratch.Set "prev" false }} +{{ $current.Scratch.Set "getNext" false }} + +{{ $current.Scratch.Set "nextPage" false }} +{{ $current.Scratch.Set "prevPage" false }} + +{{ template "menu-filetree-np" dict "sect" .Site.Home.Sections "current" $current "site" $site }} + +{{ define "menu-filetree-np" }} + {{ $current := .current }} + {{ $site := .site }} + + {{ $sortBy := (default "title" .current.Site.Params.geekdocFileTreeSortBy | lower) }} + {{ range .sect.GroupBy "Weight" }} + {{ $rangeBy := .ByTitle }} + + {{ if eq $sortBy "title" }} + {{ $rangeBy = .ByTitle }} + {{ else if eq $sortBy "linktitle" }} + {{ $rangeBy = .ByLinkTitle }} + {{ else if eq $sortBy "date" }} + {{ $rangeBy = .ByDate }} + {{ else if eq $sortBy "publishdate" }} + {{ $rangeBy = .ByPublishDate }} + {{ else if eq $sortBy "expirydate" }} + {{ $rangeBy = .ByExpiryDate }} + {{ else if eq $sortBy "lastmod" }} + {{ $rangeBy = .ByLastmod }} + {{ else if eq $sortBy "title_reverse" }} + {{ $rangeBy = .ByTitle.Reverse }} + {{ else if eq $sortBy "linktitle_reverse" }} + {{ $rangeBy = .ByLinkTitle.Reverse }} + {{ else if eq $sortBy "date_reverse" }} + {{ $rangeBy = .ByDate.Reverse }} + {{ else if eq $sortBy "publishdate_reverse" }} + {{ $rangeBy = .ByPublishDate.Reverse }} + {{ else if eq $sortBy "expirydate_reverse" }} + {{ $rangeBy = .ByExpiryDate.Reverse }} + {{ else if eq $sortBy "lastmod_reverse" }} + {{ $rangeBy = .ByLastmod.Reverse }} + {{ end }} + + {{ range $rangeBy }} + {{ $current.Scratch.Set "current" $current }} + {{ $current.Scratch.Set "site" $site }} + + {{ if not .Params.geekdocHidden }} + {{ $numberOfPages := (add (len .Pages) (len .Sections)) }} + {{ $site := $current.Scratch.Get "site" }} + {{ $this := . }} + {{ $current := $current.Scratch.Get "current" }} + + {{ $current.Scratch.Set "refName" (partial "utils/title" .) }} + {{ $name := $current.Scratch.Get "refName" }} + + {{ if $current.Scratch.Get "getNext" }} + {{ if or $this.Content $this.Params.geekdocFlatSection }} + {{ $current.Scratch.Set "nextPage" (dict "name" $name "this" $this) }} + {{ $current.Scratch.Set "getNext" false }} + {{ end }} + {{ end }} + + {{ if eq $current.RelPermalink $this.RelPermalink }} + {{ $current.Scratch.Set "prevPage" ($current.Scratch.Get "prev") }} + {{ $current.Scratch.Set "getNext" true }} + {{ end }} + + {{ if or $this.Content $this.Params.geekdocFlatSection }} + {{ $current.Scratch.Set "prev" (dict "name" $name "this" $this) }} + {{ end }} + + {{ $sub := and (ne $numberOfPages 0) (not .Params.geekdocFlatSection) }} + {{ if $sub }} + {{ template "menu-filetree-np" dict "sect" .Pages "current" $current }} + {{ end }} + {{ end }} + {{ end }} + + {{ end }} +{{ end }} + + + {{ with ($current.Scratch.Get "prevPage") }} + + gdoc_arrow_left_alt + {{ .name }} + + {{ end }} + + + {{ with ($current.Scratch.Get "nextPage") }} + + {{ .name }} + gdoc_arrow_right_alt + + {{ end }} + diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-filetree.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-filetree.html index e236392b3..e51a5de0c 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-filetree.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/menu-filetree.html @@ -8,7 +8,7 @@
- {{ if (default true .Site.Params.GeekdocBackToTop) }} + {{ if (default true .Site.Params.geekdocBackToTop) }}
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/site-header.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/site-header.html index 5e88360e8..07aabf1b0 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/site-header.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/partials/site-header.html @@ -13,11 +13,11 @@ {{ end }}
- + {{ .Root.Site.Title }} @@ -47,7 +47,7 @@ - + {{ i18n "button_homepage" }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/posts/list.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/posts/list.html index 25a77eb55..ca0ea73a4 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/posts/list.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/posts/list.html @@ -29,6 +29,7 @@

{{ end }} + {{ partial "pagination.html" . }} {{ end }} {{ define "post-tag" }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/avatar.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/avatar.html new file mode 100644 index 000000000..1d6442982 --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/avatar.html @@ -0,0 +1,57 @@ +{{- $source := ($.Page.Resources.ByType "image").GetMatch (printf "%s" (.Get "name")) }} +{{- $customAlt := .Get "alt" }} +{{- $customSize := .Get "size" | lower }} +{{- $customAnchor := default "smart" (.Get "anchor") | title }} +{{- $data := newScratch }} + +{{- with $source }} + {{- $caption := default .Title $customAlt }} + {{- $isSVG := (eq .MediaType.SubType "svg") }} + {{- $origin := . -}} + + {{- if $isSVG }} + {{- $data.SetInMap "size" "tiny" "160" }} + {{- $data.SetInMap "size" "small" "300" }} + {{- $data.SetInMap "size" "medium" "600" }} + {{- $data.SetInMap "size" "large" "900" }} + {{- else }} + {{- $data.SetInMap "size" "tiny" (printf "160x160 %s" $customAnchor) }} + {{- $data.SetInMap "size" "small" (printf "300x300 %s" $customAnchor) }} + {{- $data.SetInMap "size" "medium" (printf "600x600 %s" $customAnchor) }} + {{- $data.SetInMap "size" "large" (printf "900x900 %s" $customAnchor) }} + {{- end -}} + + +{{- end }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/columns.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/columns.html index 5a7bb62b7..a359e4146 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/columns.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/columns.html @@ -6,9 +6,9 @@
- {{ range split .Inner "<--->" }} + {{- range split .Inner "<--->" }}
- {{ . | $.Page.RenderString }} + {{ . | $.Page.RenderString -}}
- {{ end }} + {{- end }}
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/expand.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/expand.html index da82c4942..0ab3d2a3c 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/expand.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/expand.html @@ -6,6 +6,6 @@
- {{ .Inner | $.Page.RenderString | htmlUnescape | safeHTML }} + {{ .Inner | $.Page.RenderString }}

diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/img.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/img.html index f630d83cc..f0bbb6b00 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/img.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/img.html @@ -1,55 +1,64 @@ {{- $source := ($.Page.Resources.ByType "image").GetMatch (printf "%s" (.Get "name")) }} {{- $customAlt := .Get "alt" }} {{- $customSize := .Get "size" | lower }} -{{- $lazyLoad := default (default true $.Site.Params.GeekdocImageLazyLoading) (.Get "lazy") }} +{{- $lazyLoad := default (default true $.Site.Params.geekdocImageLazyLoading) (.Get "lazy") }} +{{- $data := newScratch }} {{- with $source }} {{- $caption := default .Title $customAlt }} + {{- $isSVG := (eq .MediaType.SubType "svg") }} + {{- $origin := . }} - {{- $origin := .Permalink }} - {{- $profile := (.Fill "180x180 Center").Permalink }} - {{- $tiny := (.Resize "320x").Permalink }} - {{- $small := (.Resize "600x").Permalink }} - {{- $medium := (.Resize "1200x").Permalink }} - {{- $large := (.Resize "1800x").Permalink }} - - {{- $size := dict "origin" $origin "profile" $profile "tiny" $tiny "small" $small "medium" $medium "large" $large }} - + {{- if $isSVG }} + {{- $data.SetInMap "size" "tiny" "320" }} + {{- $data.SetInMap "size" "small" "600" }} + {{- $data.SetInMap "size" "medium" "1200" }} + {{- $data.SetInMap "size" "large" "1800" }} + {{- else }} + {{- $data.SetInMap "size" "tiny" "320x"}} + {{- $data.SetInMap "size" "small" "600x" }} + {{- $data.SetInMap "size" "medium" "1200x" }} + {{- $data.SetInMap "size" "large" "1800x" }} + {{- end -}}
-
+
- {{- end }} - /> + {{- end }} {{ $caption }} - {{- if not (eq $customSize "profile") }} - {{- with $caption }} -
- {{ . }} - {{- with $source.Params.credits }} - {{ printf " (%s)" . | $.Page.RenderString }} - {{- end }} -
- {{- end }} + {{- with $caption }} +
+ {{ . }} + {{- with $source.Params.credits }} + {{ printf " (%s)" . | $.Page.RenderString }} + {{- end }} +
{{- end }}
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/progress.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/progress.html new file mode 100644 index 000000000..244f92e91 --- /dev/null +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/progress.html @@ -0,0 +1,23 @@ +{{- $value := default 0 (.Get "value") -}} +{{- $title := .Get "title" -}} +{{- $icon := .Get "icon" -}} + + +
+
+
+ {{ with $icon -}} + + {{- end }} + {{ with $title }}{{ . }}{{ end }} +
+
{{ $value }}%
+
+
+
+
+
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/propertylist.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/propertylist.html index eddae6d5a..b97faf7a2 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/propertylist.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/propertylist.html @@ -1,21 +1,29 @@ {{- $name := .Get "name" -}} +{{- $sort := .Get "sort" -}} +{{- $order := default "asc" (.Get "order") -}} +{{- $showAnchor := (and (default true .Page.Params.geekdocAnchor) (default true .Page.Site.Params.geekdocAnchor)) -}} {{- if .Site.Data.properties }}
{{- with (index .Site.Data.properties (split $name ".")) }} - {{- range $key, $value := .properties }} -
- {{ $key }} - {{- if $value.required }} + {{- $properties := .properties }} + {{- with $sort }} + {{- $properties = (sort $properties . $order) }} + {{- end }} + {{- range $properties }} + {{- $uniqueAnchor := anchorize (printf "%s-%s" $name .name) | safeHTML }} +
+ {{ .name }} + {{- if .required }} {{ i18n "propertylist_required" | lower }} - {{ else }} + {{- else }} {{ i18n "propertylist_optional" | lower }} {{- end }} - {{- with $value.type }} + {{- with .type }} {{ . }} {{- end }} - {{- with $value.tags }} + {{- with .tags }} {{- $tags := . }} {{- if reflect.IsMap $tags }} {{- $tags = (index $tags $.Site.Language.Lang) }} @@ -24,20 +32,24 @@ {{ . }} {{- end }} {{- end }} + {{- if $showAnchor }} + + + + {{- end }}
- {{- with $value.description }} + {{- with .description }} {{- $desc := . }} {{- if reflect.IsMap $desc }} {{- $desc = (index $desc $.Site.Language.Lang) }} {{- end }} - {{ $desc | $.Page.RenderString }} - {{ end }} + {{- end }}
- {{- with default "none" ($value.defaultValue | string) }} + {{- with default "none" (.defaultValue | string) }} {{ i18n "propertylist_default" | title }}: {{ . }} {{- end }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tab.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tab.html index 4eb1b445a..90b27274d 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tab.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tab.html @@ -1,12 +1,12 @@ -{{ if .Parent }} - {{ $name := .Get 0 }} - {{ $group := printf "tabs-%s" (.Parent.Get 0) }} +{{- if .Parent }} + {{- $name := .Get 0 }} + {{- $group := printf "tabs-%s" (.Parent.Get 0) }} - {{ if not (.Parent.Scratch.Get $group) }} - {{ .Parent.Scratch.Set $group slice }} - {{ end }} + {{- if not (.Parent.Scratch.Get $group) }} + {{- .Parent.Scratch.Set $group slice }} + {{- end }} - {{ .Parent.Scratch.Add $group (dict "Name" $name "Content" .Inner) }} -{{ else }} + {{- .Parent.Scratch.Add $group (dict "Name" $name "Content" .Inner) }} +{{- else }} {{ errorf "%q: 'tab' shortcode must be inside 'tabs' shortcode" .Page.Path }} -{{ end }} +{{- end }} diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tabs.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tabs.html index fcefb0d0e..7d8671ec4 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tabs.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/tabs.html @@ -1,10 +1,10 @@ -{{ if .Inner }}{{ end }} -{{ $id := .Get 0 }} -{{ $group := printf "tabs-%s" $id }} +{{- if .Inner }}{{ end }} +{{- $id := .Get 0 }} +{{- $group := printf "tabs-%s" $id }}
- {{ range $index, $tab := .Scratch.Get $group }} + {{- range $index, $tab := .Scratch.Get $group }} {{ .Content | $.Page.RenderString }}
- {{ end }} + {{- end }}
diff --git a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/toc-tree.html b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/toc-tree.html index 74492fde4..4c81b5b59 100644 --- a/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/toc-tree.html +++ b/doc/src/main/hugo/themes/hugo-geekdoc/layouts/shortcodes/toc-tree.html @@ -1,8 +1,10 @@ -{{- $tocLevels := default (default 6 .Site.Params.GeekdocToC) .Page.Params.GeekdocToC }} +{{- $current := . }} +{{- $tocLevels := default (default 6 .Site.Params.geekdocToC) .Page.Params.geekdocToC }} +{{- $sortBy := (default (default "title" .Site.Params.geekdocFileTreeSortBy) (.Get "sortBy") | lower) }} {{- if $tocLevels }}
- {{ template "toc-tree" dict "sect" .Page.Pages }} + {{ template "toc-tree" dict "sect" .Page.Pages "current" $current "sortBy" $sortBy }}
{{- end }} @@ -10,25 +12,55 @@ {{- define "toc-tree" }}