using afPlastic
** Contains contextual information about a Fancordion command.
const class CommandCtx {
private static const PlasticCompiler compiler := PlasticCompiler()
** The command URI.
const Str cmdUri
** The *scheme* portion of the command URI:
**
** [text]`scheme:path`
const Str cmdScheme
** The *path* portion of the command URI (minus the scheme):
**
** [text]`scheme:path`
const Str cmdPath
** The *text* portion of the command:
**
** [text]`scheme:path`
**
** For table column commands this is the column text.
const Str cmdText
** The table being processed. Only available in table commands.
const Str[][]? table
** The 0-based table row index. Only available in table row commands.
const Int? tableRowIdx
** The columns that make up the current table row. Only available in table row commands.
const Str[]? tableCols
** The 0-based table col index. Only available in table row commands.
const Int? tableColIdx
** Is set to 'true' if there has been previous errors in the fixture and this command should be ignored.
const Bool ignore
internal new make(Str cmdScheme, Str cmdPath, Str cmdText, Str[][]? table, Int? tableRowIdx, Int? tableColIdx, Bool ignore) {
this.cmdUri = "${cmdScheme}:${cmdPath}"
this.cmdScheme = cmdScheme
this.cmdPath = cmdPath
this.cmdText = cmdText
this.table = table?.dup?.with { removeAt(0) } // the first row is always empty, from the --- ---
this.tableRowIdx= tableRowIdx
this.tableCols = this.table?.getSafe(tableRowIdx)
this.tableColIdx= tableColIdx
this.ignore = ignore
}
** Applies Fancordion variables to the given str.
** Specifically it replaces portions of the string with:
**
** - '#TEXT -> cmdText.toCode'
** - '#FIXTURE -> "fixture"'
**
** The following are replaced in table commands:
** - '#COL -> tableColIdx'
** - '#COL[0] -> tableCols[0].toCode'
** - '#COL[1] -> tableCols[1].toCode'
** - '#COL[n] -> tableCols[n].toCode'
** - '#COLS -> tableCols.toCode'
** - '#ROW -> tableRowIdx'
** - '#ROW[0] -> table[0].toCode'
** - '#ROW[1] -> table[1].toCode'
** - '#ROW[n] -> table[n].toCode'
** - '#ROWS -> table.toCode'
Str applyVariables(Str text := cmdPath) {
if (text.isEmpty) return text // a tiny optimisation
text = text.replace("#TEXT", cmdText.toCode)
text = text.replace("#FIXTURE", "fixture")
if (table != null) {
text = text.replace("#ROWS", table.toCode)
table.each |row, i| {
text = text.replace("#ROW[${i}]", table[i].toCode)
}
if (tableRowIdx != null)
text = text.replace("#ROW", tableRowIdx.toCode)
if (tableCols != null) {
text = text.replace("#COLS", tableCols.toCode)
tableCols.each |col, i| {
text = text.replace("#COL[${i}]", tableCols[i].toCode)
}
if (tableColIdx != null) {
if (text.contains("#N")) {
Log.get("afFancordion").warn("#N Macro is deprecated - use #COL instead")
text = text.replace("#N", "#COL")
}
text = text.replace("#COL", tableColIdx.toCode)
}
}
}
return text
}
** Executes the given code against the fixture instance. Example:
**
** executeOnFixture(fixture, "echo()") --> fixture.echo()
Void executeOnFixture(Obj fixture, Str code) {
model := PlasticClassModel("FixtureExecutor", false).extend(FixtureExecutor#)
body := isSlotty(fixture, code)
? "fixture := (${fixture.typeof.qname}) obj;\nfixture.${code}"
: "fixture := (${fixture.typeof.qname}) obj;\n${code}"
model.overrideMethod(FixtureExecutor#executeOn, body)
if (fixture.typeof.pod != null)
model.usingPod(fixture.typeof.pod)
help := (FixtureExecutor) compiler.compileModel(model).make
help.executeOn(fixture)
}
** Executes the given code on the fixture instance and returns a value. Example:
**
** getFromFixture(fixture, "toStr()") --> return fixture.toStr()
Obj? getFromFixture(Obj fixture, Str code) {
model := PlasticClassModel("FixtureExecutor", false).extend(FixtureExecutor#)
body := isSlotty(fixture, code)
? "fixture := (${fixture.typeof.qname}) obj;\nreturn fixture.${code}"
: "fixture := (${fixture.typeof.qname}) obj;\nreturn ${code}"
model.overrideMethod(FixtureExecutor#getFrom, body)
if (fixture.typeof.pod != null)
model.usingPod(fixture.typeof.pod)
help := (FixtureExecutor) compiler.compileModel(model).make
return help.getFrom(fixture)
}
internal static Bool isSlotty(Obj fixture, Str code) {
slotName := ""
code.chars.eachWhile |char->Bool?| {
if (char.isAlphaNum || char == ':') {
slotName += char.toChar
return null
}
return true
}
if (slotName.contains("::"))
return false
return fixture.typeof.slot(slotName, false) != null
}
}
@NoDoc
abstract class FixtureExecutor {
virtual Void executeOn(Obj obj) { }
virtual Obj? getFrom (Obj obj) { null }
}