6.7.0 - Alpha11 - 控制台相关方法打印巨型数据时根据上限适当省略部分内容以降低打印耗时
This commit is contained in:
@@ -9,6 +9,7 @@ import org.autojs.autojs.extension.ArrayExtensions.toNativeObject
|
||||
import org.autojs.autojs.extension.FlexibleArray.Companion.component1
|
||||
import org.autojs.autojs.extension.FlexibleArray.Companion.component2
|
||||
import org.autojs.autojs.extension.ScriptableExtensions.prop
|
||||
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
|
||||
import org.autojs.autojs.rhino.ProxyObject
|
||||
import org.autojs.autojs.rhino.ProxyObject.Companion.AUGMENTED_CUSTOM_TO_STRING_KEY
|
||||
import org.autojs.autojs.rhino.ProxyObject.Companion.AUGMENTED_OBJECT_KEY
|
||||
@@ -27,6 +28,8 @@ import org.autojs.autojs.util.RhinoUtils
|
||||
import org.autojs.autojs.util.RhinoUtils.callFunction
|
||||
import org.autojs.autojs.util.RhinoUtils.callPrototypeFunction
|
||||
import org.autojs.autojs.util.RhinoUtils.callToStringFunction
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceIntNumber
|
||||
import org.autojs.autojs.util.RhinoUtils.javaArrayToString
|
||||
import org.autojs.autojs.util.RhinoUtils.js_array_isArray
|
||||
import org.autojs.autojs.util.RhinoUtils.js_json_stringify
|
||||
@@ -52,8 +55,8 @@ import org.mozilla.javascript.TopLevel.Builtins
|
||||
import org.mozilla.javascript.Undefined.isUndefined
|
||||
import org.mozilla.javascript.Wrapper
|
||||
import org.mozilla.javascript.regexp.NativeRegExp
|
||||
import java.util.*
|
||||
import java.util.function.Supplier
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Created by SuperMonster003 on Jun 10, 2024.
|
||||
@@ -206,6 +209,8 @@ object Inspect : Augmentable(), Invokable {
|
||||
"null" to "bold",
|
||||
)
|
||||
|
||||
private val DIGITS_REGEX = Regex("\\d+")
|
||||
|
||||
/**
|
||||
* Echos the value of a value. Trys to print the value out
|
||||
* in the best way possible given the different types.
|
||||
@@ -222,34 +227,46 @@ object Inspect : Augmentable(), Invokable {
|
||||
val opt = when {
|
||||
options.isJsNullish() -> Options()
|
||||
options is NativeObject -> Options().also { opt ->
|
||||
val optionShowHidden = options.prop("showHidden")
|
||||
if (!optionShowHidden.isJsNullish()) {
|
||||
opt.showHidden = Context.toBoolean(optionShowHidden)
|
||||
}
|
||||
val optionDepth = options.prop("depth")
|
||||
if (!optionDepth.isJsNullish()) {
|
||||
opt.depth = Context.toNumber(optionDepth).roundToInt()
|
||||
}
|
||||
val optionColors = options.prop("colors")
|
||||
if (!optionColors.isJsNullish()) {
|
||||
opt.colors = Context.toBoolean(optionColors)
|
||||
}
|
||||
options.inquire("showHidden", transformer = ::coerceBoolean)?.let { opt.showHidden = it }
|
||||
options.inquire("depth", transformer = ::coerceIntNumber)?.let { opt.depth = it }
|
||||
options.inquire("colors", transformer = ::coerceBoolean)?.let { opt.colors = it }
|
||||
options.inquire("maxArrayItems", transformer = ::coerceIntNumber)?.let { opt.maxArrayItems = it }
|
||||
options.inquire("maxObjectKeys", transformer = ::coerceIntNumber)?.let { opt.maxObjectKeys = it }
|
||||
options.inquire("maxStringLength", transformer = ::coerceIntNumber)?.let { opt.maxStringLength = it }
|
||||
options.inquire("customInspect", transformer = ::coerceBoolean)?.let { opt.customInspect = it }
|
||||
}
|
||||
else -> throw WrappedIllegalArgumentException("Argument \"options\" ${options.jsBrief()} for util.inspect must be a JavaScript Object")
|
||||
}
|
||||
val ctx = Ctx().also {
|
||||
it.showHidden = opt.showHidden
|
||||
it.depth = opt.depth
|
||||
it.colors = opt.colors
|
||||
it.stylize = when (it.colors) {
|
||||
true -> { str, styleType ->
|
||||
styles[styleType]?.let { style ->
|
||||
colors[style]?.let { color ->
|
||||
color.joinToString(str) { c -> "\u001b[${c}m" }
|
||||
} ?: throw WrappedIllegalArgumentException("Invalid style type: $styleType")
|
||||
} ?: str
|
||||
val ctx = Ctx().also { c ->
|
||||
opt.showHidden?.let { c.showHidden = it }
|
||||
opt.depth?.let { c.depth = it }
|
||||
opt.colors?.let { c.colors = it }
|
||||
opt.customInspect?.let { c.customInspect = it }
|
||||
opt.maxArrayItems?.let { c.maxArrayItems = it }
|
||||
opt.maxObjectKeys?.let { c.maxObjectKeys = it }
|
||||
opt.maxStringLength?.let { c.maxStringLength = it }
|
||||
|
||||
// Pre-compute style cache to avoid constructing escape sequences every time during stylize.
|
||||
// zh-CN: 预计算样式缓存, 避免每次 stylize 都构造转义序列.
|
||||
if (c.colors) {
|
||||
val cache = HashMap<String, Pair<String, String>>(styles.size)
|
||||
styles.forEach { (styleType, colorName) ->
|
||||
colors[colorName]?.let { codes ->
|
||||
val prefix = "\u001b[${codes.first()}m"
|
||||
val suffix = "\u001b[${codes.last()}m"
|
||||
cache[styleType] = prefix to suffix
|
||||
}
|
||||
}
|
||||
else -> { str, _ -> str }
|
||||
c.styleCache = cache
|
||||
c.stylize = { str, styleType ->
|
||||
run {
|
||||
val st = styleType ?: return@run str
|
||||
val pair = c.styleCache[st] ?: return@run str
|
||||
pair.first + str + pair.second
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.stylize = { str, _ -> str }
|
||||
}
|
||||
}
|
||||
return formatValue(ctx, obj, ctx.depth)
|
||||
@@ -272,12 +289,16 @@ object Inspect : Augmentable(), Invokable {
|
||||
|
||||
if (value is Scriptable && value !is NativeJavaPackage) runCatching {
|
||||
(value.prop(StringReadable.KEY) as? BaseFunction)?.let { f ->
|
||||
return Context.toString(callFunction(f, null, value, arrayOf()))
|
||||
val value = Context.toString(callFunction(f, null, value, arrayOf()))
|
||||
return formatValue(ctx, value, recurseTimes)
|
||||
}
|
||||
}
|
||||
|
||||
// Provide a hook for user-specified inspect functions.
|
||||
// Check that value is an object with an inspect function on it
|
||||
// Check that value is an object with an inspect function on it.
|
||||
// zh-CN:
|
||||
// 为用户指定的 inspect 函数提供一个钩子.
|
||||
// 检查 value 是否为带有 inspect 函数的对象.
|
||||
|
||||
// noinspection JSIncompatibleTypesComparison
|
||||
if (ctx.customInspect && value is Scriptable && value.isJsNonNullObject()) {
|
||||
@@ -285,7 +306,7 @@ object Inspect : Augmentable(), Invokable {
|
||||
if (inspectFunc is BaseFunction) {
|
||||
val ret = callFunction(inspectFunc, null, value, arrayOf(recurseTimes, ctx))
|
||||
return when {
|
||||
isStringRhino(ret) -> Context.toString(ret)
|
||||
isStringRhino(ret) -> formatValue(ctx, Context.toString(ret), recurseTimes)
|
||||
else -> formatValue(ctx, ret, recurseTimes)
|
||||
}
|
||||
}
|
||||
@@ -301,8 +322,15 @@ object Inspect : Augmentable(), Invokable {
|
||||
}
|
||||
}
|
||||
|
||||
// Primitive types cannot have properties
|
||||
// Primitive types cannot have properties.
|
||||
// zh-CN: 原始类型不能有属性.
|
||||
formatPrimitive(ctx, value).takeUnless { it.isJsNullish() }?.let {
|
||||
// String truncation (avoid huge overhead caused by overlong strings).
|
||||
// zh-CN: 字符串截断 (避免超长字符串造成巨大开销).
|
||||
if (value is String && ctx.maxStringLength > 0 && it.length > ctx.maxStringLength) {
|
||||
val cut = it.substring(0, ctx.maxStringLength)
|
||||
return ctx.stylize("$cut... (${it.length - cut.length} more chars)", "string")
|
||||
}
|
||||
return Context.toString(it)
|
||||
}
|
||||
|
||||
@@ -314,7 +342,7 @@ object Inspect : Augmentable(), Invokable {
|
||||
if (value is NativeJavaPackage) return value.toString()
|
||||
if (value is NativeJavaClass) return value.toString()
|
||||
if (value.javaClass.isArray) return javaArrayToString(value)
|
||||
if (value !is ScriptableObject) return Context.toString(value)
|
||||
if (value !is ScriptableObject) return formatValue(ctx, Context.toString(value), recurseTimes)
|
||||
if (value is ProxyObject) return formatValue(ctx, value[AUGMENTED_OBJECT_KEY], recurseTimes)
|
||||
|
||||
// @Hint by SuperMonster003 on Apr 16, 2024.
|
||||
@@ -372,6 +400,7 @@ object Inspect : Augmentable(), Invokable {
|
||||
}
|
||||
|
||||
// Look up the keys of the object.
|
||||
// zh-CN: 查找对象的键.
|
||||
var keys: NativeArray = js_object_keys(value)
|
||||
val visibleKeys = newNativeObject().also { o ->
|
||||
keys.forEach { o.put(Context.toString(it), o, true) }
|
||||
@@ -380,13 +409,15 @@ object Inspect : Augmentable(), Invokable {
|
||||
keys = js_object_getOwnPropertyNames(value)
|
||||
}
|
||||
|
||||
// IE doesn't make error fields non-enumerable
|
||||
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
|
||||
// IE doesn't make error fields non-enumerable.
|
||||
// zh-CN: IE 不会使错误字段不可枚举.
|
||||
// @Reference to http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
|
||||
if (isErrorRhino(value) && (keys.contains("message") || keys.contains("description"))) {
|
||||
return formatError(value)
|
||||
}
|
||||
|
||||
// Some type of object without properties can be shortcut.
|
||||
// zh-CN: 某些没有属性的对象类型可以简化处理.
|
||||
if (keys.isEmpty) {
|
||||
when (value) {
|
||||
is BaseFunction -> return formatBaseFunction(ctx, value)
|
||||
@@ -400,28 +431,33 @@ object Inspect : Augmentable(), Invokable {
|
||||
var array = false
|
||||
var braces = listOf("{", "}")
|
||||
|
||||
// Make Array say that they are Array
|
||||
// Make Array say that they are Array.
|
||||
// zh-CN: 让数组表明它们是数组.
|
||||
if (js_array_isArray(value)) {
|
||||
array = true
|
||||
braces = listOf("[", "]")
|
||||
}
|
||||
|
||||
// Make functions say that they are functions
|
||||
// Make functions say that they are functions.
|
||||
// zh-CN: 让函数表明它们是函数.
|
||||
if (value is BaseFunction) {
|
||||
base = " ${getBaseFunctionFormattedName(value)}"
|
||||
}
|
||||
|
||||
// Make RegExps say that they are RegExps
|
||||
// Make RegExps say that they are RegExps.
|
||||
// zh-CN: 让正则表达式表明它们是正则表达式.
|
||||
if (value is NativeRegExp) {
|
||||
base = " ${getRegExpFormattedName(value)}"
|
||||
}
|
||||
|
||||
// Make dates with properties first say the date
|
||||
// Make dates with properties first say the date.
|
||||
// zh-CN: 让带属性的日期先说明它们是日期.
|
||||
if (value is NativeDate) {
|
||||
base = " ${getDateFormattedName(value, true)}"
|
||||
}
|
||||
|
||||
// Make error with a message first say the error
|
||||
// Make error with a message first say the error.
|
||||
// zh-CN: 让带消息的错误先说明它们是错误.
|
||||
if (value is NativeError) {
|
||||
base = " ${getErrorFormattedName(value)}"
|
||||
}
|
||||
@@ -437,13 +473,21 @@ object Inspect : Augmentable(), Invokable {
|
||||
}
|
||||
}
|
||||
|
||||
// Identity-based collection, O(1) contains/add.
|
||||
// zh-CN: 基于身份的集合, O(1) contains/add.
|
||||
ctx.seen.add(value)
|
||||
|
||||
val output: List<String> = if (array) {
|
||||
formatArray(ctx, value as NativeArray, recurseTimes, visibleKeys, keys)
|
||||
} else {
|
||||
try {
|
||||
keys.map { key -> formatProperty(ctx, value, recurseTimes, visibleKeys, Context.toString(key), false) }
|
||||
// Object properties limit.
|
||||
// zh-CN: 对象属性上限.
|
||||
val limit = if (ctx.maxObjectKeys > 0) ctx.maxObjectKeys else Int.MAX_VALUE
|
||||
keys.asSequence()
|
||||
.take(limit)
|
||||
.map { key -> formatProperty(ctx, value, recurseTimes, visibleKeys, Context.toString(key), false) }
|
||||
.toList()
|
||||
} catch (err: Exception) {
|
||||
if (isJavaObjectRhino(value)) {
|
||||
RhinoUtils.unwrap(value).let {
|
||||
@@ -453,15 +497,18 @@ object Inspect : Augmentable(), Invokable {
|
||||
}
|
||||
return toJavaObjectString(value)
|
||||
}
|
||||
return try {
|
||||
val value = try {
|
||||
Context.toString(value).ifEmpty { "" }
|
||||
} catch (e: Exception) {
|
||||
Context.toString(callPrototypeFunction(Builtins.Object, "toString", value))
|
||||
}
|
||||
return formatValue(ctx, value, recurseTimes)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.seen.removeLastOrNull()
|
||||
// With Set, just remove directly.
|
||||
// zh-CN: 与 Set 搭配, 直接移除即可.
|
||||
ctx.seen.remove(value)
|
||||
|
||||
return reduceToSingleString(output, base, braces)
|
||||
}
|
||||
@@ -484,16 +531,54 @@ object Inspect : Augmentable(), Invokable {
|
||||
|
||||
private fun formatArray(ctx: Ctx, value: NativeArray, recurseTimes: Int?, visibleKeys: NativeObject, keys: NativeArray): List<String> {
|
||||
val output = mutableListOf<String>()
|
||||
for (i in 0 until value.length) {
|
||||
|
||||
// Array elements upper limit display (head/tail summary).
|
||||
// zh-CN: 数组元素上限显示 (head/tail 摘要).
|
||||
val len = value.length.toInt()
|
||||
val max = if (ctx.maxArrayItems > 0) ctx.maxArrayItems else Int.MAX_VALUE
|
||||
// First 100.
|
||||
// zh-CN: 前 100.
|
||||
val head = minOf(len, minOf(max, 100))
|
||||
val tail = when {
|
||||
// When exceeding, show last 20.
|
||||
// zh-CN: 多出时, 末尾展示 20.
|
||||
len > max -> minOf(20, max - head).coerceAtLeast(0)
|
||||
else -> 0
|
||||
}
|
||||
|
||||
// Show head first.
|
||||
// zh-CN: 先展示 head.
|
||||
for (i in 0 until head) {
|
||||
val key = Context.toString(i)
|
||||
output += when (js_object_hasOwnProperty(value, key)) {
|
||||
true -> formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
// Middle summary.
|
||||
// zh-CN: 中间摘要.
|
||||
if (len > head + tail) {
|
||||
val omitted = len - head - tail
|
||||
output += ctx.stylize("... $omitted more items ...", "special")
|
||||
}
|
||||
|
||||
// Then show tail.
|
||||
// zh-CN: 再展示 tail.
|
||||
for (i in (len - tail) until len) {
|
||||
if (i < head) continue
|
||||
val key = Context.toString(i)
|
||||
output += when (js_object_hasOwnProperty(value, key)) {
|
||||
true -> formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
// Append non-numeric keys.
|
||||
// zh-CN: 补充非数字键.
|
||||
keys.forEach { key ->
|
||||
val niceKey = Context.toString(key)
|
||||
if (!niceKey.matches(Regex("\\d+"))) {
|
||||
if (!DIGITS_REGEX.matches(niceKey)) {
|
||||
output.add(formatProperty(ctx, value, recurseTimes, visibleKeys, niceKey, true))
|
||||
}
|
||||
}
|
||||
@@ -524,11 +609,16 @@ object Inspect : Augmentable(), Invokable {
|
||||
}
|
||||
|
||||
if (str.isEmpty()) {
|
||||
if (ctx.seen.indexOf(desc.prop("value")) < 0) {
|
||||
val v = desc.prop("value")
|
||||
// Use identity Set to detect cycles, avoid O(n) indexOf.
|
||||
// zh-CN: 使用身份 Set 检测循环, 避免 O(n) indexOf.
|
||||
if (v is ScriptableObject && ctx.seen.contains(v)) {
|
||||
str = ctx.stylize("[Circular]", "special")
|
||||
} else {
|
||||
str = if (recurseTimes == null) {
|
||||
formatValue(ctx, desc.prop("value"), null)
|
||||
formatValue(ctx, v, null)
|
||||
} else {
|
||||
formatValue(ctx, desc.prop("value"), recurseTimes - 1)
|
||||
formatValue(ctx, v, recurseTimes - 1)
|
||||
}
|
||||
if (str.contains("\n")) {
|
||||
str = if (array) {
|
||||
@@ -537,12 +627,10 @@ object Inspect : Augmentable(), Invokable {
|
||||
str.split("\n").joinToString("\n") { " $it" }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
str = ctx.stylize("[Circular]", "special")
|
||||
}
|
||||
}
|
||||
if (name.isJsNullish()) {
|
||||
if (array && key.matches(Regex("\\d+"))) {
|
||||
if (array && DIGITS_REGEX.matches(key)) {
|
||||
return str
|
||||
}
|
||||
name = Context.toString(js_json_stringify(key))
|
||||
@@ -637,9 +725,13 @@ object Inspect : Augmentable(), Invokable {
|
||||
}
|
||||
|
||||
class Options {
|
||||
var colors = false
|
||||
var depth: Int = 2
|
||||
var showHidden = false
|
||||
var colors: Boolean? = null
|
||||
var depth: Int? = null
|
||||
var showHidden: Boolean? = null
|
||||
var customInspect: Boolean? = null
|
||||
var maxArrayItems: Int? = null
|
||||
var maxObjectKeys: Int? = null
|
||||
var maxStringLength: Int? = null
|
||||
}
|
||||
|
||||
class Ctx {
|
||||
@@ -647,7 +739,18 @@ object Inspect : Augmentable(), Invokable {
|
||||
var depth: Int = 2
|
||||
var showHidden = false
|
||||
var customInspect = false
|
||||
val seen = mutableListOf<ScriptableObject>()
|
||||
var maxArrayItems: Int = 10_000
|
||||
var maxObjectKeys: Int = 10_000
|
||||
var maxStringLength: Int = 100_000
|
||||
|
||||
// Identity-based collection to avoid O(n^2).
|
||||
// zh-CN: 以对象身份比较的集合, 避免 O(n^2).
|
||||
val seen: MutableSet<ScriptableObject> = Collections.newSetFromMap(IdentityHashMap())
|
||||
|
||||
// Pre-compute style cache to avoid constructing escape sequences every time during stylize.
|
||||
// zh-CN: 预计算样式缓存, 避免每次 stylize 都构造转义序列.
|
||||
var styleCache: Map<String, Pair<String, String>> = emptyMap()
|
||||
|
||||
lateinit var stylize: (str: String, styleType: String?) -> String
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ import org.autojs.autojs.extension.AnyExtensions.isJsSymbol
|
||||
import org.autojs.autojs.extension.AnyExtensions.isJsUndefined
|
||||
import org.autojs.autojs.extension.AnyExtensions.jsBrief
|
||||
import org.autojs.autojs.extension.FlexibleArray
|
||||
import org.autojs.autojs.extension.FlexibleArray.Companion.component1
|
||||
import org.autojs.autojs.extension.FlexibleArray.Companion.component2
|
||||
import org.autojs.autojs.extension.FlexibleArray.Companion.component3
|
||||
import org.autojs.autojs.extension.NumberExtensions.jsString
|
||||
import org.autojs.autojs.extension.ScriptableExtensions.defineProp
|
||||
import org.autojs.autojs.extension.ScriptableExtensions.prop
|
||||
@@ -343,31 +346,101 @@ object Util : Augmentable() {
|
||||
@JvmStatic
|
||||
@RhinoFunctionBody
|
||||
fun formatRhino(vararg args: Any?): String {
|
||||
var index = 1
|
||||
if (args.isEmpty()) return ""
|
||||
|
||||
val first = args.first()
|
||||
if (first !is String) return args.joinToString(" ") { inspectRhino(it) }
|
||||
val first = args[0]
|
||||
if (first !is String) {
|
||||
return args.joinToString(" ") { inspectRhino(it) }
|
||||
}
|
||||
|
||||
var str = first.replace(Regex("%[sdj%]")) { matchResult ->
|
||||
if (matchResult.value == "%%") return@replace "%"
|
||||
if (index >= args.size) return@replace matchResult.value
|
||||
val argsSize = args.size
|
||||
var index = 1
|
||||
val fmt = first
|
||||
val len = fmt.length
|
||||
// Estimate some margin to reduce expansion.
|
||||
// zh-CN: 预估一些余量, 减少扩容.
|
||||
val sb = StringBuilder(len + 16)
|
||||
|
||||
when (matchResult.value) {
|
||||
"%s" -> Context.toString(args[index++])
|
||||
"%d" -> Context.toNumber(args[index++]).jsString
|
||||
"%j" -> try {
|
||||
Context.toString(js_json_stringify(args[index++]))
|
||||
} catch (e: Exception) {
|
||||
"[Circular]"
|
||||
var i = 0
|
||||
while (i < len) {
|
||||
val ch = fmt[i]
|
||||
if (ch != '%') {
|
||||
sb.append(ch)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// '%' at last char -> append '%'.
|
||||
if (i + 1 >= len) {
|
||||
sb.append('%')
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
when (val spec = fmt[i + 1]) {
|
||||
'%' -> {
|
||||
sb.append('%')
|
||||
i += 2
|
||||
}
|
||||
's' -> {
|
||||
if (index >= argsSize) {
|
||||
// Keep the placeholder as is when no corresponding parameter exists.
|
||||
// zh-CN: 没有对应参数, 保持占位符原样.
|
||||
sb.append('%').append('s')
|
||||
} else {
|
||||
sb.append(Context.toString(args[index++]))
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
'd' -> {
|
||||
if (index >= argsSize) {
|
||||
sb.append('%').append('d')
|
||||
} else {
|
||||
sb.append(Context.toNumber(args[index++]).jsString)
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
'j' -> {
|
||||
if (index >= argsSize) {
|
||||
sb.append('%').append('j')
|
||||
} else {
|
||||
try {
|
||||
sb.append(Context.toString(js_json_stringify(args[index++])))
|
||||
} catch (_: Throwable) {
|
||||
sb.append("[Circular]")
|
||||
}
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
else -> {
|
||||
// Keep unknown placeholders as is.
|
||||
// zh-CN: 未知占位符, 按原样输出.
|
||||
sb.append('%').append(spec)
|
||||
i += 2
|
||||
}
|
||||
else -> matchResult.value
|
||||
}
|
||||
}
|
||||
|
||||
args.copyOfRange(index, args.size).forEach { x -> str += " ${inspectRhino(x)}" }
|
||||
val formatted = inspectRhino(sb.toString())
|
||||
|
||||
return str
|
||||
return when {
|
||||
index >= argsSize -> formatted
|
||||
else -> {
|
||||
// Append remaining parameters, separated by spaces.
|
||||
// zh-CN: 追加剩余参数, 以空格分隔.
|
||||
val out = StringBuilder(formatted.length + 1 + (argsSize - index) * 8)
|
||||
out.append(formatted)
|
||||
out.append(' ')
|
||||
// Append the first one first, then add spaces for the rest to avoid checking leading spaces in the loop.
|
||||
// zh-CN: 为避免在循环中判断前导空格, 这里先追加第一个, 再对剩余的加空格.
|
||||
out.append(inspectRhino(args[index++]))
|
||||
while (index < argsSize) {
|
||||
out.append(' ')
|
||||
out.append(inspectRhino(args[index++]))
|
||||
}
|
||||
out.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
Reference in New Issue
Block a user