diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/ValueFactory.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/ValueFactory.kt index 72cd5df1..04968825 100644 --- a/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/ValueFactory.kt +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/ValueFactory.kt @@ -15,6 +15,7 @@ import eu.iamgio.quarkdown.function.expression.Expression import eu.iamgio.quarkdown.function.expression.eval import eu.iamgio.quarkdown.function.reflect.FromDynamicType import eu.iamgio.quarkdown.function.reflect.ReflectionUtils +import eu.iamgio.quarkdown.function.value.data.EvaluableString import eu.iamgio.quarkdown.function.value.data.Lambda import eu.iamgio.quarkdown.function.value.data.Range import eu.iamgio.quarkdown.lexer.Lexer @@ -185,6 +186,25 @@ object ValueFactory { values.find { it.name.replace("_", "").equals(raw, ignoreCase = true) } ?.let { EnumValue(it) } + /** + * Generates an [EvaluableString]. + * Contrary to [String], an [EvaluableString] natively supports function calls and scripting evaluation. + * @param raw raw value to convert to a string expression + * @param context context to evaluate the raw value in + * @return a new string expression value that wraps the evaluated content of [raw] + * @see eval for the evaluation process + */ + @FromDynamicType(EvaluableString::class, requiresContext = true) + fun evaluableString( + raw: String, + context: Context, + ): ObjectValue = + ObjectValue( + EvaluableString( + eval(raw, context).unwrappedValue.toString(), + ), + ) + /** * @param lexer lexer to use to tokenize content * @param context context to retrieve the pipeline from, which allows parsing and function expansion diff --git a/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/data/EvaluableString.kt b/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/data/EvaluableString.kt new file mode 100644 index 00000000..0658490c --- /dev/null +++ b/core/src/main/kotlin/eu/iamgio/quarkdown/function/value/data/EvaluableString.kt @@ -0,0 +1,14 @@ +package eu.iamgio.quarkdown.function.value.data + +import eu.iamgio.quarkdown.function.value.ValueFactory + +/** + * A [String] wrapper that, when used as a function parameter, lets [ValueFactory.evaluableString] evaluate the raw content. + * This allows function calls and other scripting techniques to be used executed within the string itself, + * which would otherwise be natively unsupported unless the [String] argument is inlined (not used as a block argument). + * Inline string evaluation is handled directly by the parser [eu.iamgio.quarkdown.parser.BlockTokenParser]). + * This is used for example in the `.code` stdlib function. + * @param content unwrapped string content, already evaluated + * @see ValueFactory.evaluableString + */ +data class EvaluableString(val content: String) diff --git a/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt b/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt index 7ff7d2f5..637ec11e 100644 --- a/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt +++ b/stdlib/src/main/kotlin/eu/iamgio/quarkdown/stdlib/Text.kt @@ -1,7 +1,6 @@ package eu.iamgio.quarkdown.stdlib import eu.iamgio.quarkdown.ast.InlineMarkdownContent -import eu.iamgio.quarkdown.ast.MarkdownContent import eu.iamgio.quarkdown.ast.base.block.Code import eu.iamgio.quarkdown.ast.quarkdown.inline.TextTransform import eu.iamgio.quarkdown.ast.quarkdown.inline.TextTransformData @@ -9,10 +8,10 @@ import eu.iamgio.quarkdown.context.MutableContext import eu.iamgio.quarkdown.function.reflect.Injected import eu.iamgio.quarkdown.function.reflect.Name import eu.iamgio.quarkdown.function.value.NodeValue +import eu.iamgio.quarkdown.function.value.data.EvaluableString import eu.iamgio.quarkdown.function.value.data.Range import eu.iamgio.quarkdown.function.value.wrappedAsValue import eu.iamgio.quarkdown.misc.Color -import eu.iamgio.quarkdown.util.toPlainText /** * `Text` stdlib module exporter. @@ -53,24 +52,41 @@ fun text( /** * Creates a code block. Contrary to its standard Markdown implementation with backtick/tilde fences, - * this function accepts Markdown content as its body, hence it can be used - for example - - * in combination with [read] to load code from file. + * this function accepts function calls within its [code] argument, + * hence it can be used - for example - in combination with [read] to load code from file. + * + * Examples: + * + * Load from file: + * ``` + * .code {kotlin} focus:{2..5} + * .read {snippet.kt} + * ``` + * + * Load dynamically: + * ``` + * .function {mycode} {mylang} + * source: + * code {mylang} + * .source + * ``` + * * @param language optional language of the code * @param showLineNumbers whether to show line numbers * @param focusedLines range of lines to focus on. No lines are focused if unset. Supports open ranges. * Note: HTML rendering requires [showLineNumbers] to be enabled. - * @param body code content + * @param code code content */ fun code( @Injected context: MutableContext, @Name("lang") language: String? = null, @Name("linenumbers") showLineNumbers: Boolean = true, @Name("focus") focusedLines: Range? = null, - body: MarkdownContent, + code: EvaluableString, ): NodeValue { context.attributes.hasCode = true // Allows code highlighting. return Code( - body.children.toPlainText(), + code.content, language, showLineNumbers, focusedLines, diff --git a/stdlib/src/test/kotlin/eu/iamgio/quarkdown/stdlib/TextTest.kt b/stdlib/src/test/kotlin/eu/iamgio/quarkdown/stdlib/TextTest.kt index 970d3a31..6a127d20 100644 --- a/stdlib/src/test/kotlin/eu/iamgio/quarkdown/stdlib/TextTest.kt +++ b/stdlib/src/test/kotlin/eu/iamgio/quarkdown/stdlib/TextTest.kt @@ -1,10 +1,10 @@ package eu.iamgio.quarkdown.stdlib -import eu.iamgio.quarkdown.ast.MarkdownContent import eu.iamgio.quarkdown.ast.base.block.Code import eu.iamgio.quarkdown.ast.base.inline.Text import eu.iamgio.quarkdown.context.MutableContext import eu.iamgio.quarkdown.flavor.quarkdown.QuarkdownFlavor +import eu.iamgio.quarkdown.function.value.data.EvaluableString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -21,7 +21,7 @@ class TextTest { MutableContext(QuarkdownFlavor), language = "kotlin", showLineNumbers = false, - body = MarkdownContent(listOf(Text("fun foo() = 1"))), + code = EvaluableString("fun foo() = 1"), ) val node = code.unwrappedValue diff --git a/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt b/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt index acca9545..b0e184f1 100644 --- a/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt +++ b/test/src/test/kotlin/eu/iamgio/quarkdown/test/FullPipelineTest.kt @@ -359,7 +359,7 @@ class FullPipelineTest { .1 """.trimIndent(), ) { - assertEquals("
Line 1\nLine 2
", it) + assertEquals("
Line 1${System.lineSeparator()}Line 2
", it) } }