6.7.0 - Alpha8 - 新增 cvt/fmt 模块及 (cvt/fmt/s13n).bytes 方法, 用于字节数据处理

This commit is contained in:
SuperMonster003
2025-10-26 14:30:09 +08:00
parent 1b5558a075
commit fef3323423
15 changed files with 1102 additions and 97 deletions

View File

@@ -3,8 +3,13 @@
"v6.7.0": {
"released_date": "2025/10/26",
"feature": [
"cvt 模块, 用于数据单位转换 (参阅 项目文档 > [单位转换](https://docs.autojs6.com/#/cvt))",
"fmt 模块, 用于数据格式化 (参阅 项目文档 > [格式化](https://docs.autojs6.com/#/fmt))",
"zip 模块, 用于文件压缩与解压缩操作 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) (参阅 项目文档 > [Zip](https://docs.autojs6.com/#/zip))",
"mediainfo 模块, 用于查看媒体文件的详细信息 (参阅 项目文档 > [媒体信息](https://docs.autojs6.com/#/mediainfo))",
"cvt.bytes 方法, 用于字节数据单位转换 (参阅 项目文档 > [单位转换](https://docs.autojs6.com/#/cvt))",
"fmt.bytes 方法, 用于字节数据格式化 (参阅 项目文档 > [数据格式化](https://docs.autojs6.com/#/fmt))",
"s13n.bytes 方法, 用于标准化字节数据 (参阅 项目文档 > [标准化](https://docs.autojs6.com/#/s13n))",
"UiObject#isShifted 方法, 用于检测控件位置变化",
"device.getSharedDeviceId 方法, 用于跨应用获取统一共享设备 ID _[`issue #455`](http://issues.autojs6.com/455)_",
"structuredClone 全局方法, 用于深拷贝 JavaScript 对象 (参阅 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/structuredClone))",

View File

@@ -17,6 +17,7 @@ import org.mozilla.javascript.NativeError
import org.mozilla.javascript.Scriptable
import org.mozilla.javascript.UniqueTag.NOT_FOUND
import org.mozilla.javascript.Wrapper
import java.math.BigInteger
object AnyExtensions {
@@ -82,8 +83,8 @@ object AnyExtensions {
fun <T> T?.jsSanitize() = if (this.isJsNullish()) null else this
fun Any?.jsUnwrapped(): Any? = when (this) {
is String -> this
is ConsString -> this.toString()
is String, is ConsString -> Context.toString(this)
is BigInteger -> this
is Number -> Context.toNumber(this)
is Boolean -> Context.toBoolean(this)
is Wrapper -> this.unwrap().jsUnwrapped()

View File

@@ -4,7 +4,7 @@ import android.content.Context
import android.content.res.AssetManager
import android.text.TextUtils
import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.core.pref.Language
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes
import org.autojs.autojs.tool.Func1
import org.autojs.autojs.util.EnvironmentUtils
import org.autojs.autojs.util.FileUtils
@@ -19,8 +19,6 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.charset.Charset
import kotlin.math.ln
import kotlin.math.pow
/**
* Created by Stardust on Apr 1, 2017.
@@ -60,14 +58,9 @@ object PFiles {
@JvmStatic
fun create(path: String): Boolean {
val f = File(path)
return if (path.endsWith(separator)) {
f.mkdir()
} else {
try {
f.createNewFile()
} catch (e: IOException) {
false
}
return when {
path.endsWith(separator) -> f.mkdir()
else -> runCatching { f.createNewFile() }.isSuccess
}
}
@@ -95,7 +88,7 @@ object PFiles {
fun ensureDir(path: String): Boolean {
val i = path.lastIndexOf(separator)
return if (i >= 0) {
val folder = path.substring(0, i)
val folder = path.take(i)
val file = File(folder)
file.exists() || file.mkdirs()
} else false
@@ -355,7 +348,7 @@ object PFiles {
val fileName = getName(filePath)
var b = fileName.lastIndexOf('.')
if (b < 0) b = fileName.length
return fileName.substring(0, b)
return fileName.take(b)
}
fun copyAssetToTmpFile(context: Context, path: String): File {
@@ -450,14 +443,18 @@ object PFiles {
}
@JvmStatic
fun getHumanReadableSize(bytes: Long): String {
val unit = 1024
if (bytes < unit) return "$bytes B"
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
@Suppress("SpellCheckingInspection")
val pre = "KMGTPE".substring(exp - 1, exp)
return String.format(Language.getPrefLanguage().locale, "%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
@JvmOverloads
fun getHumanReadableSize(bytes: Long, useIecIdentifier: Boolean = false): String {
return Bytes.string(
source = bytes.toDouble(),
fromUnit = "B",
toUnit = "AUTO",
useIecIdentifier = useIecIdentifier,
useSpace = true,
fractionDigits = 1,
trimTrailingZero = false,
signature = "PFiles.getHumanReadableSize",
)
}
@JvmStatic
@@ -511,10 +508,6 @@ object PFiles {
@JvmStatic
fun closeSilently(closeable: Closeable?) {
try {
closeable?.close()
} catch (ignored: IOException) {
/* Ignored. */
}
runCatching { closeable?.close() }
}
}

View File

@@ -44,6 +44,7 @@ import org.autojs.autojs.runtime.api.augment.colors.Color
import org.autojs.autojs.runtime.api.augment.colors.Colors
import org.autojs.autojs.runtime.api.augment.console.Console
import org.autojs.autojs.runtime.api.augment.continuation.Continuation
import org.autojs.autojs.runtime.api.augment.converter.Converter
import org.autojs.autojs.runtime.api.augment.cryptyo.Crypto
import org.autojs.autojs.runtime.api.augment.device.Device
import org.autojs.autojs.runtime.api.augment.dialogs.Dialogs
@@ -52,6 +53,7 @@ import org.autojs.autojs.runtime.api.augment.events.Events
import org.autojs.autojs.runtime.api.augment.events.Keys
import org.autojs.autojs.runtime.api.augment.files.Files
import org.autojs.autojs.runtime.api.augment.floaty.Floaty
import org.autojs.autojs.runtime.api.augment.formatter.Formatter
import org.autojs.autojs.runtime.api.augment.global.Global
import org.autojs.autojs.runtime.api.augment.global.GlobalClasses
import org.autojs.autojs.runtime.api.augment.global.IsNullish
@@ -160,6 +162,8 @@ import org.autojs.autojs.runtime.api.Toaster as ApiToaster
import org.autojs.autojs.runtime.api.UI as ApiUI
import org.autojs.autojs.runtime.api.Util as ApiUtil
import org.autojs.autojs.runtime.api.augment.autojs.Version as AutojsVersion
import org.autojs.autojs.runtime.api.augment.converter.Bytes as BytesCvt
import org.autojs.autojs.runtime.api.augment.formatter.Bytes as BytesFmt
import org.autojs.autojs.runtime.api.augment.global.Legacy as GlobalLegacy
import org.autojs.autojs.runtime.api.augment.notice.Channel as NoticeChannel
import org.autojs.autojs.runtime.api.augment.util.Inspect as UtilInspect
@@ -714,20 +718,20 @@ class ScriptRuntime private constructor(builder: Builder) {
private fun augment(target: ScriptableObject) {
Global(this).assignWithGlobal(target, topLevelScope, GlobalClasses)
GlobalLegacy(this).assignWithGlobal(target, topLevelScope)
IsNullish.augmentWithGlobal(target, topLevelScope, false)
Util.augmentWithGlobal(target, topLevelScope, util, true).apply {
UtilJava.augmentWithGlobal(this, topLevelScope, false)
UtilVersion.augmentWithGlobal(this, topLevelScope, false)
UtilVersionCodes.augmentWithGlobal(this, topLevelScope, VersionCodesInfo.obj, false)
UtilInspect.augmentWithGlobal(this, topLevelScope, false)
UtilMorseCode.augmentWithGlobal(this, topLevelScope, false)
Global(this).assignWithRuntime(target, this, GlobalClasses)
GlobalLegacy(this).assignWithRuntime(target, this)
IsNullish.augmentWithRuntime(target, this, false)
Util.augmentWithRuntime(target, this, util, true).also { util ->
UtilJava.augmentWithRuntime(util, this, false)
UtilVersion.augmentWithRuntime(util, this, false)
UtilVersionCodes.augmentWithRuntime(util, this, VersionCodesInfo.obj, false)
UtilInspect.augmentWithRuntime(util, this, false)
UtilMorseCode.augmentWithRuntime(util, this, false)
}
Species.augmentWithGlobal(target, topLevelScope, true)
Species.augmentWithRuntime(target, this, true)
App(this).augment(target, app, true).also { augmentedApp = it }
Autojs(this).augment(target, true).also { augmentedAutojs = it }.apply {
AutojsVersion.augmentWithGlobal(this, topLevelScope, false)
Autojs(this).augment(target, true).also { augmentedAutojs = it }.also { autojs ->
AutojsVersion.augmentWithRuntime(autojs, this, false)
}
Shell(this).augment(target, true)
Timers(this).augment(target, timers, true)
@@ -735,26 +739,32 @@ class ScriptRuntime private constructor(builder: Builder) {
Automator(this).augment(target, true)
Selector(this).augment(target, true, READONLY)
Events(this).augment(target, events, true)
Keys.augmentWithGlobal(target, topLevelScope, true)
Keys.augmentWithRuntime(target, this, true)
Images(this).augment(target, true)
Ocr(this).augment(target, true).apply {
OcrMLKit(this@ScriptRuntime).augment(this, false).also { augmentedOcrMLKit = it }
OcrPaddle(this@ScriptRuntime).augment(this, false).also { augmentedOcrPaddle = it }
OcrRapid(this@ScriptRuntime).augment(this, false).also { augmentedOcrRapid = it }
Ocr(this).augment(target, true).also { ocr ->
OcrMLKit(this).augment(ocr, false).also { augmentedOcrMLKit = it }
OcrPaddle(this).augment(ocr, false).also { augmentedOcrPaddle = it }
OcrRapid(this).augment(ocr, false).also { augmentedOcrRapid = it }
}
Barcode(this).augment(target, true)
QrCode(this).augment(target, true)
Threads(this).augment(target, threads, true)
UI(this).proxying(target, ui, true)
Colors.augmentWithGlobal(target, topLevelScope, listOf(colors, Colors), true)
Color.augmentWithGlobal(target, topLevelScope, false)
Colors.augmentWithRuntime(target, this, listOf(colors, Colors), true)
Color.augmentWithRuntime(target, this, false)
Tasks(this).augment(target, true)
Dialogs(this).augment(target, true)
Continuation(this).augment(target, js_mod_continuation, true, READONLY)
Http(this).augment(target, http, true)
Web(this).augment(target, true)
WebSocket(this).augment(target, WebSocketFields, false)
S13n.augmentWithGlobal(target, topLevelScope, true)
S13n.augmentWithRuntime(target, this, true)
Converter.augmentWithRuntime(target, this, true).also { cvt ->
BytesCvt.augmentWithRuntime(cvt, this, false)
}
Formatter.augmentWithRuntime(target, this, true).also { fmt ->
BytesFmt.augmentWithRuntime(fmt, this, false)
}
Console(this).proxying(target, console, true).also { consoleProxyObject = it }
Plugins(this).augment(target, true)
Arrayx(this).augment(target, false)
@@ -762,29 +772,29 @@ class ScriptRuntime private constructor(builder: Builder) {
Mathx(this).augment(target, false)
Jsox(this).augment(target, true)
Files(this).augment(target, files, true)
Crypto.augmentWithGlobal(target, topLevelScope, CoreCrypto, true)
Crypto.augmentWithRuntime(target, this, CoreCrypto, true)
RootAutomator(this).augment(target, false)
Engines(this).augment(target, true)
Floaty(this).augment(target, true)
Storages.augmentWithGlobal(target, topLevelScope, true)
Storages.augmentWithRuntime(target, this, true)
Device(this).augment(target, device, true)
Recorder(this).augment(target, recorder, true)
Toast(this).augment(target, true)
Media.augmentWithGlobal(target, topLevelScope, media, true)
Sensors.augmentWithGlobal(target, topLevelScope, sensors, true)
Base64.augmentWithGlobal(target, topLevelScope, true)
Notice(this).augment(target, true).apply {
NoticeChannel(this@ScriptRuntime).augment(this, false)
Media.augmentWithRuntime(target, this, media, true)
Sensors.augmentWithRuntime(target, this, sensors, true)
Base64.augmentWithRuntime(target, this, true)
Notice(this).augment(target, true).also { notice ->
NoticeChannel(this).augment(notice, false)
}
Shizuku(this).augment(target, shizuku, true)
OpenCC.augmentWithGlobal(target, topLevelScope, true)
OpenCC.augmentWithRuntime(target, this, true)
Mime(this).augment(target, mime, true)
SysProps(this).augment(target, true)
SQLite(this).augment(target, true)
Zip(this).augment(target, true)
NanoID.augmentWithGlobal(target, topLevelScope, true)
Pinyin.augmentWithGlobal(target, topLevelScope, true)
Pinyin4j.augmentWithGlobal(target, topLevelScope, true)
NanoID.augmentWithRuntime(target, this, true)
Pinyin.augmentWithRuntime(target, this, true)
Pinyin4j.augmentWithRuntime(target, this, true)
Mediainfo(this).augment(target, true)
augmentedApp.defineProp(Autojs::class.java.simpleName.lowercase(), augmentedAutojs)

View File

@@ -192,8 +192,9 @@ class Files(private val scriptRuntime: ScriptRuntime) {
return PFiles.isEmptyDir(path(path))
}
fun getHumanReadableSize(bytes: Long): String {
return PFiles.getHumanReadableSize(bytes)
@JvmOverloads
fun getHumanReadableSize(bytes: Long, useIecIdentifier: Boolean = false): String {
return PFiles.getHumanReadableSize(bytes, useIecIdentifier)
}
fun getSimplifiedPath(path: String?): String {

View File

@@ -138,19 +138,19 @@ abstract class Augmentable(private val scriptRuntime: ScriptRuntime? = null) : F
fun originateKeyName() = also { mIsOriginalKeyName = true }
fun augmentWithGlobal(target: Scriptable, specifiedGlobal: ScriptableObject, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0): ScriptableObject {
return augment(target, withDollarPrefix, additionalAttributes, specifiedGlobal)
fun augmentWithRuntime(target: Scriptable, specifiedRuntime: ScriptRuntime, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0): ScriptableObject {
return augment(target, withDollarPrefix, additionalAttributes, specifiedRuntime)
}
fun augmentWithGlobal(target: Scriptable, specifiedGlobal: ScriptableObject, proto: Any, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0): ScriptableObject {
return augment(target, proto, withDollarPrefix, additionalAttributes, specifiedGlobal)
fun augmentWithRuntime(target: Scriptable, specifiedRuntime: ScriptRuntime, proto: Any, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0): ScriptableObject {
return augment(target, proto, withDollarPrefix, additionalAttributes, specifiedRuntime)
}
fun augment(target: Scriptable, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0, specifiedGlobal: ScriptableObject? = null): ScriptableObject {
return augment(target, emptyList<Any>(), withDollarPrefix, additionalAttributes, specifiedGlobal)
fun augment(target: Scriptable, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0, specifiedRuntime: ScriptRuntime? = null): ScriptableObject {
return augment(target, emptyList<Any>(), withDollarPrefix, additionalAttributes, specifiedRuntime)
}
fun augment(target: Scriptable, proto: Any, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0, specifiedGlobal: ScriptableObject? = null): ScriptableObject {
fun augment(target: Scriptable, proto: Any, withDollarPrefix: Boolean = true, additionalAttributes: Int = 0, specifiedRuntime: ScriptRuntime? = null): ScriptableObject {
val callFunc: (args: Array<out Any?>) -> Any? = { args ->
try {
(this as Invokable).invoke(*args)
@@ -191,7 +191,7 @@ abstract class Augmentable(private val scriptRuntime: ScriptRuntime? = null) : F
else -> RhinoUtils.newObject(target)
}
assign(newObj, proto, specifiedGlobal)
assign(newObj, proto, specifiedRuntime)
val keys = mutableListOf(key)
if (withDollarPrefix) keys += "\$$key"
@@ -203,17 +203,17 @@ abstract class Augmentable(private val scriptRuntime: ScriptRuntime? = null) : F
return newObj
}
fun assignWithGlobal(target: ScriptableObject, specifiedGlobal: ScriptableObject, proto: Any? = null) {
assign(target, proto, specifiedGlobal)
fun assignWithRuntime(target: ScriptableObject, specifiedRuntime: ScriptRuntime, proto: Any? = null) {
assign(target, proto, specifiedRuntime)
}
/**
* When the subclass calls assign, the value of key will be ignored.
* zh-CN: 子类调用 assign 时将忽略 key 的值.
*/
fun assign(target: ScriptableObject, proto: Any? = null, specifiedGlobal: ScriptableObject? = null) {
fun assign(target: ScriptableObject, proto: Any? = null, specifiedRuntime: ScriptRuntime? = null) {
val global: ScriptableObject = specifiedGlobal ?: scriptRuntime?.topLevelScope ?: ScriptableObject.getTopLevelScope(target) as ScriptableObject
val global: ScriptableObject = (specifiedRuntime ?: scriptRuntime)?.topLevelScope ?: ScriptableObject.getTopLevelScope(target) as ScriptableObject
val protos = when (proto) {
null -> emptyList()

View File

@@ -40,7 +40,13 @@ import org.autojs.autojs.util.RhinoUtils.withRhinoContext
import org.autojs.autojs.util.StringUtils.lowercaseFirstChar
import org.autojs.autojs.util.StringUtils.uppercaseFirstChar
import org.autojs.autojs6.R
import org.mozilla.javascript.*
import org.mozilla.javascript.BaseFunction
import org.mozilla.javascript.Context
import org.mozilla.javascript.NativeArray
import org.mozilla.javascript.NativeError
import org.mozilla.javascript.NativeObject
import org.mozilla.javascript.ScriptableObject
import org.mozilla.javascript.Undefined
@Suppress("unused", "UNUSED_PARAMETER")
class Console(scriptRuntime: ScriptRuntime) : AugmentableProxy(scriptRuntime) {
@@ -226,38 +232,38 @@ class Console(scriptRuntime: ScriptRuntime) : AugmentableProxy(scriptRuntime) {
@JvmStatic
@RhinoRuntimeFunctionInterface
fun log(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = unwrapArguments(args) {
scriptRuntime.console.log(Util.formatRhino(*it))
fun log(scriptRuntime: ScriptRuntime, args: Array<out Any?>) {
return scriptRuntime.console.log(Util.formatRhino(*args))
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun verbose(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = unwrapArguments(args) {
scriptRuntime.console.verbose(Util.formatRhino(*it))
fun verbose(scriptRuntime: ScriptRuntime, args: Array<out Any?>) {
return scriptRuntime.console.verbose(Util.formatRhino(*args))
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun info(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = unwrapArguments(args) {
scriptRuntime.console.info(Util.formatRhino(*it))
fun info(scriptRuntime: ScriptRuntime, args: Array<out Any?>) {
return scriptRuntime.console.info(Util.formatRhino(*args))
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun warn(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = unwrapArguments(args) {
scriptRuntime.console.warn(Util.formatRhino(*it))
fun warn(scriptRuntime: ScriptRuntime, args: Array<out Any?>) {
return scriptRuntime.console.warn(Util.formatRhino(*args))
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun error(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = unwrapArguments(args) {
scriptRuntime.console.error(Util.formatRhino(*it))
fun error(scriptRuntime: ScriptRuntime, args: Array<out Any?>) {
return scriptRuntime.console.error(Util.formatRhino(*args))
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun print(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = unwrapArguments(args) {
scriptRuntime.console.print(Log.DEBUG, Util.formatRhino(*it))
fun print(scriptRuntime: ScriptRuntime, args: Array<out Any?>) {
return scriptRuntime.console.print(Log.DEBUG, Util.formatRhino(*args))
}
@JvmStatic

View File

@@ -0,0 +1,215 @@
@file:Suppress("MayBeConstant")
package org.autojs.autojs.runtime.api.augment.converter
import org.autojs.autojs.annotation.RhinoSingletonFunctionInterface
import org.autojs.autojs.extension.AnyExtensions.isJsBoolean
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
import org.autojs.autojs.extension.AnyExtensions.isJsNumber
import org.autojs.autojs.extension.AnyExtensions.isJsObject
import org.autojs.autojs.extension.AnyExtensions.isJsString
import org.autojs.autojs.extension.AnyExtensions.jsBrief
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
import org.autojs.autojs.runtime.api.augment.Augmentable
import org.autojs.autojs.runtime.api.augment.Invokable
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.AUTO
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.DEFAULT_BYTES_STRICT
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.IEC_DIV
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.SI_DIV
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough.LOOSE
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough.NONE
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough.STRICT
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.UNITS
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
import org.mozilla.javascript.ScriptableObject
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes as CoreBytes
object Bytes : Augmentable(), Invokable {
override val selfAssignmentProperties = listOf(
"UNITS" to UNITS,
"AUTO" to AUTO,
"IEC_DIV" to IEC_DIV,
"SI_DIV" to SI_DIV,
)
override val selfAssignmentFunctions = listOf(
::strict.name,
::loose.name,
)
override fun invoke(vararg args: Any?): Any = ensureArgumentsLengthInRange(args, 1..4) { call(args) }
@JvmStatic
@RhinoSingletonFunctionInterface
fun call(args: Array<out Any?>, tough: Tough = NONE): Any = ensureArgumentsLengthInRange(args, 1..4) { argList ->
val (arg0, arg1, arg2, arg3) = argList
when (argList.size) {
4 -> when {
arg3.isJsObject() -> {
val opts = arg3 as ScriptableObject
listOf("source", "fromUnit", "toUnit", "options").forBytesNumber(
source = arg0,
fromUnit = opts.inquire("fromUnit", arg1),
toUnit = opts.inquire("toUnit", arg2),
fractionDigits = opts.inquire("fractionDigits"),
autoCarryThreshold = opts.inquire("autoCarryThreshold"),
strict = opts.inquire("strict"),
tough = tough,
)
}
arg3.isJsBoolean() -> {
listOf("source", "fromUnit", "toUnit", "useIecIdentifier").forBytesNumber(
source = arg0,
fromUnit = arg1,
toUnit = arg2,
strict = tough == STRICT,
)
}
arg3.isJsNumber() -> {
listOf("source", "fromUnit", "toUnit", "fractionDigits").forBytesNumber(
source = arg0,
fromUnit = arg1,
toUnit = arg2,
fractionDigits = arg3,
strict = tough == STRICT,
)
}
else -> throw IllegalArgumentException("Invalid argument[3] ${arg3.jsBrief()} for ${Converter.key}.bytes")
}
3 -> when {
arg2.isJsObject() -> {
val opts = arg2 as ScriptableObject
listOf("source", "toUnit", "options").forBytesNumber(
source = arg0,
fromUnit = opts.inquire("fromUnit"),
toUnit = opts.inquire("toUnit", arg1),
fractionDigits = opts.inquire("fractionDigits"),
autoCarryThreshold = opts.inquire("autoCarryThreshold"),
strict = opts.inquire("strict"),
tough = tough,
)
}
arg2.isJsString() -> {
listOf("source", "fromUnit", "toUnit").forBytesNumber(
source = arg0,
fromUnit = arg1,
toUnit = arg2,
strict = tough == STRICT,
)
}
arg2.isJsBoolean() -> {
listOf("source", "toUnit", "useIecIdentifier").forBytesNumber(
source = arg0,
toUnit = arg1,
strict = tough == STRICT,
)
}
arg2.isJsNumber() -> {
listOf("source", "toUnit", "fractionDigits").forBytesNumber(
source = arg0,
toUnit = arg1,
fractionDigits = arg2,
strict = tough == STRICT,
)
}
else -> throw IllegalArgumentException("Invalid argument[2] ${arg2.jsBrief()} for ${Converter.key}.bytes")
}
2 -> when {
arg1.isJsObject() -> {
val opts = arg1 as ScriptableObject
listOf("source", "options").forBytesNumber(
source = arg0,
fromUnit = opts.inquire("fromUnit"),
toUnit = opts.inquire("toUnit"),
fractionDigits = opts.inquire("fractionDigits"),
autoCarryThreshold = opts.inquire("autoCarryThreshold"),
strict = opts.inquire("strict"),
tough = tough,
)
}
arg1.isJsString() -> {
listOf("source", "toUnit").forBytesNumber(
source = arg0,
toUnit = arg1,
strict = tough == STRICT,
)
}
arg1.isJsBoolean() -> {
listOf("source", "useIecIdentifier").forBytesNumber(
source = arg0,
strict = tough == STRICT,
)
}
arg1.isJsNumber() -> {
listOf("source", "fractionDigits").forBytesNumber(
source = arg0,
fractionDigits = arg1,
strict = tough == STRICT,
)
}
else -> throw IllegalArgumentException("Invalid argument[1] ${arg1.jsBrief()} for ${Converter.key}.bytes")
}
1 -> listOf("source").forBytesNumber(
source = arg0,
strict = tough == STRICT,
)
else -> throw IllegalArgumentException("Invalid arguments length ${argList.size} for ${Converter.key}.bytes")
}
}
@JvmStatic
@RhinoSingletonFunctionInterface
fun strict(args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..4) {
call(args, STRICT)
}
@JvmStatic
@RhinoSingletonFunctionInterface
fun loose(args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..4) {
call(args, LOOSE)
}
private fun List<String>.forBytesNumber(
source: Any? = null,
fromUnit: Any? = null,
toUnit: Any? = null,
fractionDigits: Any? = null,
autoCarryThreshold: Any? = null,
strict: Any? = null,
tough: Tough = NONE,
): Any {
val (niceStrict, signature) = when (tough) {
STRICT -> {
val signature = "${Converter.key}.bytes.strict(${this.joinToString(", ")})"
require(strict.isJsNullish()) {
"Option \"strict\" ${strict.jsBrief()} must be nullish when in strict mode for $signature"
}
true to signature
}
LOOSE -> {
val signature = "${Converter.key}.bytes.loose(${this.joinToString(", ")})"
require(strict.isJsNullish()) {
"Option \"strict\" ${strict.jsBrief()} must be nullish when in loose mode for $signature"
}
false to signature
}
NONE -> {
val signature = "${Converter.key}.bytes(${this.joinToString(", ")})"
coerceBoolean(strict, DEFAULT_BYTES_STRICT) to signature
}
}
return CoreBytes.numberRhino(
source,
fromUnit,
toUnit,
fractionDigits,
autoCarryThreshold,
niceStrict,
signature,
)
}
}

View File

@@ -0,0 +1,9 @@
package org.autojs.autojs.runtime.api.augment.converter
import org.autojs.autojs.runtime.api.augment.Augmentable
object Converter : Augmentable() {
override val key = "cvt"
}

View File

@@ -0,0 +1,455 @@
@file:Suppress("MayBeConstant")
package org.autojs.autojs.runtime.api.augment.converter.core
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
import org.autojs.autojs.extension.AnyExtensions.isJsNumber
import org.autojs.autojs.extension.AnyExtensions.jsBrief
import org.autojs.autojs.util.RhinoUtils.MAX_SAFE_INT_IEEE754_BD
import org.autojs.autojs.util.RhinoUtils.MIN_SAFE_INT_IEEE754_BD
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
import org.autojs.autojs.util.RhinoUtils.coerceIntNumber
import org.autojs.autojs.util.RhinoUtils.coerceLongNumber
import org.autojs.autojs.util.RhinoUtils.coerceNumber
import org.autojs.autojs.util.RhinoUtils.coerceString
import org.mozilla.javascript.Context
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
import kotlin.Double.Companion.NaN
import kotlin.text.RegexOption.IGNORE_CASE
object Bytes {
// @formatter:off
/**
* ```md
* | Value | IEC | Term |
* |---------|-----|--------------------|
* | 1024^1 | KiB | Kibibyte (Kilo) |
* | 1024^2 | MiB | Mebibyte (Mega) |
* | 1024^3 | GiB | Gibibyte (Giga) |
* | 1024^4 | TiB | Tebibyte (Tera) |
* | 1024^5 | PiB | Pebibyte (Peta) |
* | 1024^6 | EiB | Exbibyte (Exa) |
* | 1024^7 | ZiB | Zebibyte (Zetta) |
* | 1024^8 | YiB | Yobibyte (Yotta) |
* | 1024^9 | RiB | Robibyte (Ronna) |
* | 1024^10 | QiB | Quebibyte (Quetta) |
* ```
*/
@Suppress("SpellCheckingInspection")
@JvmField val UNITS = "KMGTPEZYRQ"
@JvmField val AUTO = "AUTO"
@JvmField val IEC_DIV = 1024L
@JvmField val SI_DIV = 1000L
@JvmField val DEFAULT_BYTES_FROM_UNIT = "B"
@JvmField val DEFAULT_BYTES_TO_UNIT = AUTO
@JvmField val DEFAULT_BYTES_USE_IEC_IDENTIFIER = false
@JvmField val DEFAULT_BYTES_USE_IEC_IDENTIFIER_FOR_STRICT = true
@JvmField val DEFAULT_BYTES_USE_SPACE = true
@JvmField val DEFAULT_BYTES_FRACTION_DIGITS = 2
@JvmField val DEFAULT_BYTES_TRIM_TRAILING_ZERO = false
@JvmField val DEFAULT_BYTES_AUTO_CARRY_THRESHOLD = IEC_DIV
@JvmField val DEFAULT_BYTES_STRICT = false
// @formatter:on
private val BI_IEC = BigInteger.valueOf(IEC_DIV)
private val BI_SI = BigInteger.valueOf(SI_DIV)
private val POW_IEC = Array(UNITS.length + 1) { i -> BI_IEC.pow(i) }
private val POW_SI = Array(UNITS.length + 1) { i -> BI_SI.pow(i) }
@JvmStatic
fun numberRhino(
source: Any? = null, /* Double */
fromUnit: Any? = null, /* String */
toUnit: Any? = null, /* String */
fractionDigits: Any? = null, /* Int */
autoCarryThreshold: Any? = null, /* Double */
strict: Any? = null, /* Boolean */
signature: String? = null,
): Any {
val r = NumberRetrievalHandler(source, fromUnit, toUnit, fractionDigits, autoCarryThreshold, strict, signature).retrieve()
return number(
r.niceSource, r.niceFromUnit, r.niceToUnit, r.niceFractionDigits, r.niceAutoCarryThreshold, r.niceStrict, signature ?: "bytes.number",
).toJavaScriptBigIntOrNumber()
}
@JvmStatic
@JvmOverloads
fun number(
source: Double,
fromUnit: String = DEFAULT_BYTES_FROM_UNIT,
toUnit: String = DEFAULT_BYTES_TO_UNIT,
fractionDigits: Int = DEFAULT_BYTES_FRACTION_DIGITS,
autoCarryThreshold: Long = DEFAULT_BYTES_AUTO_CARRY_THRESHOLD,
strict: Boolean = DEFAULT_BYTES_STRICT,
signature: String = "Bytes.number",
) = parseArguments(source, fromUnit, toUnit, fractionDigits, autoCarryThreshold, strict, signature).let { (bytesBD, baseBI, toUnit) ->
computeNumberValue(bytesBD, baseBI, toUnit, fractionDigits, autoCarryThreshold, strict, signature)
}
@JvmStatic
fun stringRhino(
source: Any? = null,
fromUnit: Any? = null,
toUnit: Any? = null,
useIecIdentifier: Any? = null,
useSpace: Any? = null,
fractionDigits: Any? = null,
trimTrailingZero: Any? = null,
autoCarryThreshold: Any? = null,
strict: Any? = null,
signature: String? = null,
): String {
val r = StringRetrievalHandler(source, fromUnit, toUnit, useIecIdentifier, useSpace, fractionDigits, trimTrailingZero, autoCarryThreshold, strict, signature).retrieve()
return string(r.niceSource, r.niceFromUnit, r.niceToUnit, r.niceUseIecIdentifier, r.niceUseSpace, r.niceFractionDigits, r.niceTrimTrailingZero, r.niceAutoCarryThreshold, r.niceStrict, signature ?: "bytes.string")
}
@JvmStatic
@JvmOverloads
fun string(
source: Double,
fromUnit: String = DEFAULT_BYTES_FROM_UNIT,
toUnit: String = DEFAULT_BYTES_TO_UNIT,
useIecIdentifier: Boolean? = null,
useSpace: Boolean = DEFAULT_BYTES_USE_SPACE,
fractionDigits: Int = DEFAULT_BYTES_FRACTION_DIGITS,
trimTrailingZero: Boolean = DEFAULT_BYTES_TRIM_TRAILING_ZERO,
autoCarryThreshold: Long = DEFAULT_BYTES_AUTO_CARRY_THRESHOLD,
strict: Boolean = DEFAULT_BYTES_STRICT,
signature: String = "Bytes.string",
): String {
val (bytesBD, baseBI, niceToUnit) = parseArguments(source, fromUnit, toUnit, fractionDigits, autoCarryThreshold, strict, signature)
val bd = computeNumberValue(bytesBD, baseBI, niceToUnit, fractionDigits, autoCarryThreshold, strict, signature)
val bdFormatted = bd.setScale(fractionDigits, RoundingMode.HALF_UP).toPlainString().let {
if (!trimTrailingZero) it else it.replace(Regex("(\\.\\d*?)0+$"), "$1").removeSuffix(".")
}
val withIec = useIecIdentifier ?: when {
strict -> DEFAULT_BYTES_USE_IEC_IDENTIFIER_FOR_STRICT
else -> DEFAULT_BYTES_USE_IEC_IDENTIFIER
}
val space = if (useSpace) " " else ""
return when {
strict -> when (niceToUnit) {
AUTO -> {
val (_, core) = autoPickBig(bytesBD, baseBI, BigDecimal.valueOf(autoCarryThreshold))
val suffix = if (core.isEmpty()) "B" else "${core}iB"
"$bdFormatted$space$suffix"
}
else -> {
val coreRaw = niceToUnit.uppercase().removeSuffix("B")
val isIEC = coreRaw.endsWith("I")
when (val core = if (isIEC) coreRaw.removeSuffix("I") else coreRaw) {
"" -> "$bdFormatted${space}B"
else -> {
val idx = UNITS.indexOf(core)
require(idx != -1) { "Argument \"toUnit\" ${toUnit.jsBrief()} is invalid${signature.toSignatureSuffix()}" }
when {
isIEC -> "$bdFormatted${space}${core}iB"
else -> "$bdFormatted${space}${core}B"
}
}
}
}
}
else -> when (niceToUnit) {
AUTO -> {
val (_, core) = autoPickBig(bytesBD, BI_IEC, BigDecimal.valueOf(autoCarryThreshold))
val suffix = if (core.isEmpty()) "B" else if (withIec) "${core}iB" else "${core}B"
"$bdFormatted$space$suffix"
}
else -> when (val core = niceToUnit.removeSuffix("B")) {
"" -> "$bdFormatted${space}B"
else -> {
val idx = UNITS.indexOf(core)
require(idx != -1) { "Argument \"toUnit\" ${toUnit.jsBrief()} is invalid${signature.toSignatureSuffix()}" }
val suffix = if (withIec) "${core}iB" else "${core}B"
"$bdFormatted$space$suffix"
}
}
}
}
}
private fun parseArguments(
source: Double,
fromUnit: String = DEFAULT_BYTES_FROM_UNIT,
toUnit: String = DEFAULT_BYTES_TO_UNIT,
fractionDigits: Int = DEFAULT_BYTES_FRACTION_DIGITS,
autoCarryThreshold: Long = DEFAULT_BYTES_AUTO_CARRY_THRESHOLD,
strict: Boolean = DEFAULT_BYTES_STRICT,
signature: String,
): UnitConversionDetails {
val signatureSuffix = signature.toSignatureSuffix()
require(!source.isNaN() && source >= 0.0) {
"Argument \"source\" ${source.jsBrief()} must be non-negative number$signatureSuffix"
}
require(fractionDigits >= 0) {
"Argument \"fractionDigits\" ${fractionDigits.jsBrief()} must be non-negative$signatureSuffix"
}
val niceToUnit = when {
strict -> toUnit.trim().uppercase()
else -> toUnit.toSiUnit()
}.takeUnless { it.isBlank() } ?: DEFAULT_BYTES_TO_UNIT
require(autoCarryThreshold > 0) {
"Argument \"autoCarryThreshold\" ${autoCarryThreshold.jsBrief()} must be a positive finite number$signatureSuffix"
}
require(autoCarryThreshold == DEFAULT_BYTES_AUTO_CARRY_THRESHOLD || niceToUnit.equals(AUTO, ignoreCase = true)) {
"Argument \"autoCarryThreshold\" is only allowed when argument \"toUnit\" is \"$AUTO\"$signatureSuffix"
}
val (bytesBD, baseBI) = run parseExactBytes@{
val valueBD = BigDecimal.valueOf(source)
val niceFromUnit = when {
strict -> fromUnit.trim()
else -> fromUnit.toSiUnit()
}.takeUnless { it.isBlank() }?.uppercase() ?: DEFAULT_BYTES_FROM_UNIT
val coreRaw = when {
niceFromUnit.endsWith("B") -> niceFromUnit.dropLast(1)
else -> niceFromUnit
}
val isIec = strict && coreRaw.endsWith("I")
val core = if (isIec) coreRaw.removeSuffix("I") else coreRaw
val base = if (!strict) BI_IEC else if (isIec) BI_IEC else BI_SI
val idx = if (core.isEmpty()) -1 else UNITS.indexOf(core)
require(core.isEmpty() || idx != -1) { "Argument \"fromUnit\" ${fromUnit.jsBrief()} is invalid$signatureSuffix" }
val factor = if (idx == -1) BigInteger.ONE else powBI(base, idx + 1)
valueBD.multiply(BigDecimal(factor)) to base
}
return UnitConversionDetails(bytesBD, baseBI, niceToUnit)
}
// Calculate the order of AUTO (avoiding errors from ln/Double), determine whether to carry early (autoCarryThreshold)
// zh-CN: 求 AUTO 的阶 (避免 ln/Double 带来的误差), 决策是否提前进位 (autoCarryThreshold).
private fun autoPickBig(bytes: BigDecimal, base: BigInteger, carryThreshold: BigDecimal): Pair<Int, String> {
if (bytes.compareTo(BigDecimal.ZERO) == 0) return 0 to ""
var exp = 0
val core = fun() = UNITS.substring((exp - 1).coerceAtLeast(0), exp)
// Find max exp such that bytes >= base^exp.
// zh-CN: 找到最大的 exp 使 bytes >= base^exp.
for (i in 1..UNITS.length) {
val th = BigDecimal(powBI(base, i))
if (bytes >= th) exp = i else break
}
if (exp > 0) {
val currentVal = bytes.divide(BigDecimal(powBI(base, exp)), DEFAULT_BYTES_FRACTION_DIGITS + 4, RoundingMode.HALF_UP)
if (currentVal >= carryThreshold && exp < UNITS.length) {
exp += 1
return exp to core()
}
}
return exp to core()
}
private fun parseSource(source: Any?, fromUnit: Any?, strict: Boolean, signatureSuffix: String): Pair<Double, String> {
var bytes: Double
var parsedUnitRaw = ""
when {
source.isJsNumber() -> {
val d = coerceNumber(source, NaN)
require(!d.isNaN() && d >= 0.0) { "Argument \"source\" ${source.jsBrief()} must be non-negative number for $signatureSuffix" }
bytes = d
}
else -> {
val s = coerceString(source, "0").trim()
val m = Regex("^(\\d+(?:\\.\\d+)?)\\s*([A-Za-z]*)$", IGNORE_CASE).find(s)
?: throw IllegalArgumentException("Invalid bytes value: \"$source\"")
bytes = try {
Context.toNumber(m.groupValues[1])
} catch (_: Throwable) {
throw IllegalArgumentException("Invalid bytes value: \"${m.groupValues[1]}\"")
}
parsedUnitRaw = m.groupValues[2]
}
}
val niceFromUnit = run parseFromUnit@{
val incoming = coerceString(fromUnit, "").let { if (strict) it.trim() else it.toSiUnit() }
val parsed = if (strict) parsedUnitRaw.trim() else parsedUnitRaw.toSiUnit()
when {
incoming.isBlank() -> parsed
parsed.isBlank() -> incoming
incoming.equals(parsed, ignoreCase = !strict) -> incoming
else -> throw IllegalArgumentException("Ambiguous \"fromUnit\" values: [ $incoming, $parsed ]$signatureSuffix")
}.ifBlank { DEFAULT_BYTES_FROM_UNIT }.uppercase()
}
return bytes to niceFromUnit
}
private fun powBI(base: BigInteger, exp: Int): BigInteger = when {
base == BI_IEC && exp in 0..UNITS.length -> POW_IEC[exp]
base == BI_SI && exp in 0..UNITS.length -> POW_SI[exp]
else -> base.pow(exp)
}
private fun computeNumberValue(
bytesBD: BigDecimal,
baseBI: BigInteger,
toUnit: String,
fractionDigits: Int,
autoCarryThreshold: Long,
strict: Boolean,
signature: String,
): BigDecimal = when {
strict -> when (toUnit) {
AUTO -> {
val (exp, _) = autoPickBig(bytesBD, baseBI, BigDecimal.valueOf(autoCarryThreshold))
val value = if (exp == 0) bytesBD else bytesBD.divide(BigDecimal(powBI(baseBI, exp)), fractionDigits, RoundingMode.HALF_UP)
value.stripTrailingZeros()
}
else -> {
val coreRaw = toUnit.uppercase().removeSuffix("B")
val isIEC = coreRaw.endsWith("I")
when (val core = if (isIEC) coreRaw.removeSuffix("I") else coreRaw) {
"" -> bytesBD.stripTrailingZeros()
else -> {
val idx = UNITS.indexOf(core)
require(idx != -1) { "Argument \"toUnit\" ${toUnit.jsBrief()} is invalid${signature.toSignatureSuffix()}" }
val base = if (isIEC) BI_IEC else BI_SI
val value = bytesBD.divide(BigDecimal(powBI(base, idx + 1)), fractionDigits, RoundingMode.HALF_UP)
value.stripTrailingZeros()
}
}
}
}
else -> when (toUnit) {
AUTO -> {
val (exp, _) = autoPickBig(bytesBD, BI_IEC, BigDecimal.valueOf(autoCarryThreshold))
val value = if (exp == 0) bytesBD else bytesBD.divide(BigDecimal(powBI(BI_IEC, exp)), fractionDigits, RoundingMode.HALF_UP)
value.stripTrailingZeros()
}
else -> when (val core = toUnit.removeSuffix("B")) {
"" -> bytesBD
else -> {
val idx = UNITS.indexOf(core)
require(idx != -1) { "Argument \"toUnit\" ${toUnit.jsBrief()} is invalid${signature.toSignatureSuffix()}" }
val value = bytesBD.divide(BigDecimal(powBI(BI_IEC, idx + 1)), fractionDigits, RoundingMode.HALF_UP)
value.stripTrailingZeros()
}
}
}
}
private fun String.toSiUnit() = this.trim().uppercase().replace(Regex("I(?=B$)"), "")
private fun String.toSignatureSuffix() = this.takeUnless { it.isBlank() }?.let { " for $it" } ?: ""
private fun BigDecimal.toJavaScriptBigIntOrNumber(): Any = when (this) {
in MIN_SAFE_INT_IEEE754_BD..MAX_SAFE_INT_IEEE754_BD -> {
this.toDouble()
}
else -> this
}
private interface RetrievalHandler {
fun retrieve(): Any
}
private open class NumberRetrievalHandler(
private val source: Any? = null, /* Double */
private val fromUnit: Any? = null, /* String */
private val toUnit: Any? = null, /* String */
private val fractionDigits: Any? = null, /* Int */
private val autoCarryThreshold: Any? = null, /* Double */
private val strict: Any? = null, /* Boolean */
private val signature: String? = null,
) : RetrievalHandler {
override fun retrieve(): NumberRhinoStandardizationDetails {
val signatureSuffix = signature?.toSignatureSuffix() ?: ""
val niceStrict = coerceBoolean(strict, DEFAULT_BYTES_STRICT)
val niceToUnit = when {
niceStrict -> coerceString(toUnit, DEFAULT_BYTES_TO_UNIT).trim().uppercase()
else -> coerceString(toUnit, DEFAULT_BYTES_TO_UNIT).toSiUnit()
}.takeIf { it.isNotEmpty() } ?: DEFAULT_BYTES_TO_UNIT
val (niceSource, niceFromUnit) = parseSource(source, fromUnit, niceStrict, signatureSuffix)
val niceFractionDigits = coerceIntNumber(fractionDigits, DEFAULT_BYTES_FRACTION_DIGITS).also {
require(it >= 0) { "Argument \"fractionDigits\" ${fractionDigits.jsBrief()} must be non-negative$signatureSuffix" }
}
require(autoCarryThreshold.isJsNullish() || niceToUnit.equals(AUTO, ignoreCase = true)) {
"Option \"autoCarryThreshold\" is only allowed when argument \"toUnit\" is \"$AUTO\"$signatureSuffix"
}
val niceAutoCarryThreshold = coerceLongNumber(autoCarryThreshold, DEFAULT_BYTES_AUTO_CARRY_THRESHOLD).also {
require(it > 0) {
"Option \"autoCarryThreshold\" ${autoCarryThreshold.jsBrief()} must be a positive finite number$signatureSuffix"
}
}
return NumberRhinoStandardizationDetails(niceSource, niceFromUnit, niceToUnit, niceFractionDigits, niceAutoCarryThreshold, niceStrict)
}
}
private class StringRetrievalHandler(
source: Any?,
fromUnit: Any?,
toUnit: Any?,
private val useIecIdentifier: Any?,
private val useSpace: Any?,
fractionDigits: Any?,
private val trimTrailingZero: Any?,
autoCarryThreshold: Any?,
strict: Any?,
private val signature: String? = null,
) : NumberRetrievalHandler(source, fromUnit, toUnit, fractionDigits, autoCarryThreshold, strict, signature) {
override fun retrieve(): StringRhinoStandardizationDetails {
val sp = super.retrieve()
val niceSource = sp.niceSource
val niceFromUnit = sp.niceFromUnit
val niceToUnit = sp.niceToUnit
val niceFractionDigits = sp.niceFractionDigits
val niceAutoCarryThreshold = sp.niceAutoCarryThreshold
val niceStrict = sp.niceStrict
val signatureSuffix = signature?.toSignatureSuffix() ?: ""
require(!niceStrict || useIecIdentifier.isJsNullish()) {
"Argument \"useIecIdentifier\" ${useIecIdentifier.jsBrief()} must be nullish when in strict mode$signatureSuffix"
}
val niceUseSpace = coerceBoolean(useSpace, DEFAULT_BYTES_USE_SPACE)
val niceTrimTrailingZero = coerceBoolean(trimTrailingZero, DEFAULT_BYTES_TRIM_TRAILING_ZERO)
val niceUseIecIdentifier = if (useIecIdentifier.isJsNullish()) null else Context.toBoolean(useIecIdentifier)
return StringRhinoStandardizationDetails(niceSource, niceFromUnit, niceToUnit, niceUseIecIdentifier, niceUseSpace, niceFractionDigits, niceTrimTrailingZero, niceAutoCarryThreshold, niceStrict)
}
}
private data class UnitConversionDetails(val bytesBD: BigDecimal, val baseBI: BigInteger, val toUnit: String)
private open class NumberRhinoStandardizationDetails(
open val niceSource: Double,
open val niceFromUnit: String,
open val niceToUnit: String,
open val niceFractionDigits: Int,
open val niceAutoCarryThreshold: Long,
open val niceStrict: Boolean,
)
private class StringRhinoStandardizationDetails(
override val niceSource: Double,
override val niceFromUnit: String,
override val niceToUnit: String,
val niceUseIecIdentifier: Boolean?,
val niceUseSpace: Boolean,
override val niceFractionDigits: Int,
val niceTrimTrailingZero: Boolean,
override val niceAutoCarryThreshold: Long,
override val niceStrict: Boolean,
) : NumberRhinoStandardizationDetails(niceSource, niceFromUnit, niceToUnit, niceFractionDigits, niceAutoCarryThreshold, niceStrict)
enum class Tough { STRICT, LOOSE, NONE }
}

View File

@@ -0,0 +1,233 @@
@file:Suppress("MayBeConstant")
package org.autojs.autojs.runtime.api.augment.formatter
import org.autojs.autojs.annotation.RhinoSingletonFunctionInterface
import org.autojs.autojs.extension.AnyExtensions.isJsBoolean
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
import org.autojs.autojs.extension.AnyExtensions.isJsNumber
import org.autojs.autojs.extension.AnyExtensions.isJsObject
import org.autojs.autojs.extension.AnyExtensions.isJsString
import org.autojs.autojs.extension.AnyExtensions.jsBrief
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
import org.autojs.autojs.runtime.api.augment.Augmentable
import org.autojs.autojs.runtime.api.augment.Invokable
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.AUTO
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.DEFAULT_BYTES_STRICT
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.IEC_DIV
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.SI_DIV
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough.LOOSE
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough.NONE
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.Tough.STRICT
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes.UNITS
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
import org.mozilla.javascript.ScriptableObject
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes as CoreBytes
object Bytes : Augmentable(), Invokable {
override val selfAssignmentProperties = listOf(
"UNITS" to UNITS,
"AUTO" to AUTO,
"IEC_DIV" to IEC_DIV,
"SI_DIV" to SI_DIV,
)
override val selfAssignmentFunctions = listOf(
::strict.name,
::loose.name,
)
override fun invoke(vararg args: Any?): String = ensureArgumentsLengthInRange(args, 1..4) { call(args) }
@JvmStatic
@RhinoSingletonFunctionInterface
fun call(args: Array<out Any?>, tough: Tough = NONE): String = ensureArgumentsLengthInRange(args, 1..4) { argList ->
val (arg0, arg1, arg2, arg3) = argList
when (argList.size) {
4 -> when {
arg3.isJsObject() -> {
val opts = arg3 as ScriptableObject
listOf("source", "fromUnit", "toUnit", "options").forBytesString(
source = arg0,
fromUnit = opts.inquire("fromUnit", arg1),
toUnit = opts.inquire("toUnit", arg2),
useIecIdentifier = opts.inquire("useIecIdentifier"),
useSpace = opts.inquire("useSpace"),
fractionDigits = opts.inquire("fractionDigits"),
trimTrailingZero = opts.inquire("trimTrailingZero"),
autoCarryThreshold = opts.inquire("autoCarryThreshold"),
strict = opts.inquire("strict"),
tough = tough,
)
}
arg3.isJsBoolean() -> {
listOf("source", "fromUnit", "toUnit", "useIecIdentifier").forBytesString(
source = arg0,
fromUnit = arg1,
toUnit = arg2,
useIecIdentifier = arg3,
strict = tough == STRICT,
)
}
arg3.isJsNumber() -> {
listOf("source", "fromUnit", "toUnit", "fractionDigits").forBytesString(
source = arg0,
fromUnit = arg1,
toUnit = arg2,
fractionDigits = arg3,
strict = tough == STRICT,
)
}
else -> throw IllegalArgumentException("Invalid argument[3] ${arg3.jsBrief()} for ${Formatter.key}.bytes")
}
3 -> when {
arg2.isJsObject() -> {
val opts = arg2 as ScriptableObject
listOf("source", "toUnit", "options").forBytesString(
source = arg0,
fromUnit = opts.inquire("fromUnit"),
toUnit = opts.inquire("toUnit", arg1),
fractionDigits = opts.inquire("fractionDigits"),
useIecIdentifier = opts.inquire("useIecIdentifier"),
useSpace = opts.inquire("useSpace"),
trimTrailingZero = opts.inquire("trimTrailingZero"),
autoCarryThreshold = opts.inquire("autoCarryThreshold"),
strict = opts.inquire("strict"),
tough = tough,
)
}
arg2.isJsString() -> {
listOf("source", "fromUnit", "toUnit").forBytesString(
source = arg0,
fromUnit = arg1,
toUnit = arg2,
strict = tough == STRICT,
)
}
arg2.isJsBoolean() -> {
listOf("source", "toUnit", "useIecIdentifier").forBytesString(
source = arg0,
toUnit = arg1,
useIecIdentifier = arg2,
strict = tough == STRICT,
)
}
arg2.isJsNumber() -> {
listOf("source", "toUnit", "fractionDigits").forBytesString(
source = arg0,
toUnit = arg1,
fractionDigits = arg2,
strict = tough == STRICT,
)
}
else -> throw IllegalArgumentException("Invalid argument[2] ${arg2.jsBrief()} for ${Formatter.key}.bytes")
}
2 -> when {
arg1.isJsObject() -> {
val opts = arg1 as ScriptableObject
listOf("source", "options").forBytesString(
source = arg0,
fromUnit = opts.inquire("fromUnit"),
toUnit = opts.inquire("toUnit"),
fractionDigits = opts.inquire("fractionDigits"),
useIecIdentifier = opts.inquire("useIecIdentifier"),
useSpace = opts.inquire("useSpace"),
trimTrailingZero = opts.inquire("trimTrailingZero"),
autoCarryThreshold = opts.inquire("autoCarryThreshold"),
strict = opts.inquire("strict"),
tough = tough,
)
}
arg1.isJsString() -> {
listOf("source", "toUnit").forBytesString(
source = arg0,
toUnit = arg1,
strict = tough == STRICT,
)
}
arg1.isJsBoolean() -> {
listOf("source", "useIecIdentifier").forBytesString(
source = arg0,
useIecIdentifier = arg1,
strict = tough == STRICT,
)
}
arg1.isJsNumber() -> {
listOf("source", "fractionDigits").forBytesString(
source = arg0,
fractionDigits = arg1,
strict = tough == STRICT,
)
}
else -> throw IllegalArgumentException("Invalid argument[1] ${arg1.jsBrief()} for ${Formatter.key}.bytes")
}
1 -> listOf("source").forBytesString(
source = arg0,
strict = tough == STRICT,
)
else -> throw IllegalArgumentException("Invalid arguments length ${argList.size} for ${Formatter.key}.bytes")
}
}
@JvmStatic
@RhinoSingletonFunctionInterface
fun strict(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..4) {
call(args, STRICT)
}
@JvmStatic
@RhinoSingletonFunctionInterface
fun loose(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..4) {
call(args, LOOSE)
}
private fun List<String>.forBytesString(
source: Any? = null,
fromUnit: Any? = null,
toUnit: Any? = null,
useIecIdentifier: Any? = null,
useSpace: Any? = null,
fractionDigits: Any? = null,
trimTrailingZero: Any? = null,
autoCarryThreshold: Any? = null,
strict: Any? = null,
tough: Tough = NONE,
): String {
val (niceStrict, signature) = when (tough) {
STRICT -> {
val signature = "${Formatter.key}.bytes.strict(${this.joinToString(", ")})"
require(strict.isJsNullish()) {
"Option \"strict\" ${strict.jsBrief()} must be nullish when in strict mode for $signature"
}
true to signature
}
LOOSE -> {
val signature = "${Formatter.key}.bytes.loose(${this.joinToString(", ")})"
require(strict.isJsNullish()) {
"Option \"strict\" ${strict.jsBrief()} must be nullish when in loose mode for $signature"
}
false to signature
}
NONE -> {
val signature = "${Formatter.key}.bytes(${this.joinToString(", ")})"
coerceBoolean(strict, DEFAULT_BYTES_STRICT) to signature
}
}
return CoreBytes.stringRhino(
source,
fromUnit,
toUnit,
useIecIdentifier,
useSpace,
fractionDigits,
trimTrailingZero,
autoCarryThreshold,
niceStrict,
signature,
)
}
}

View File

@@ -0,0 +1,9 @@
package org.autojs.autojs.runtime.api.augment.formatter
import org.autojs.autojs.runtime.api.augment.Augmentable
object Formatter : Augmentable() {
override val key = "fmt"
}

View File

@@ -1,10 +1,15 @@
@file:Suppress("MayBeConstant")
package org.autojs.autojs.runtime.api.augment.s13n
import org.autojs.autojs.annotation.RhinoFunctionBody
import org.autojs.autojs.annotation.RhinoSingletonFunctionInterface
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
import org.autojs.autojs.extension.AnyExtensions.isJsObject
import org.autojs.autojs.extension.AnyExtensions.isJsString
import org.autojs.autojs.extension.AnyExtensions.jsBrief
import org.autojs.autojs.extension.ScriptableExtensions.prop
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
import org.autojs.autojs.runtime.api.augment.Augmentable
import org.autojs.autojs.runtime.api.augment.colors.Colors
import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException
@@ -25,6 +30,7 @@ import kotlin.math.roundToInt
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.text.RegexOption.IGNORE_CASE
import android.graphics.Point as AndroidPoint
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes as CoreBytes
import org.opencv.core.Point as OpencvPoint
object S13n : Augmentable() {
@@ -60,6 +66,7 @@ object S13n : Augmentable() {
::throwable.name,
::point.name,
::time.name,
::bytes.name,
)
@JvmStatic
@@ -139,7 +146,7 @@ object S13n : Augmentable() {
} ?: when {
arg1.isJsObject() -> {
val options = arg1 as ScriptableObject
time(arrayOf(source, options.prop("fromUnit"), options.prop("toUnit")))
time(arrayOf(source, options.inquire("fromUnit"), options.inquire("toUnit")))
}
else -> {
val num = coerceNumber(source, NaN).also { require(!it.isNaN()) { "Failed to make ${arg0.jsBrief()} a number time being" } }
@@ -159,6 +166,47 @@ object S13n : Augmentable() {
}
}
@JvmStatic
@RhinoSingletonFunctionInterface
fun bytes(args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..2) { argList ->
val (arg0, arg1) = argList
when (argList.size) {
2 -> when {
arg1.isJsString() -> listOf("source", "fromUnit").forBytes(source = arg0, fromUnit = arg1)
else -> throw WrappedIllegalArgumentException("Invalid argument[1] ${arg1.jsBrief()} for $key.bytes")
}
1 -> listOf("source").forBytes(source = arg0)
else -> throw WrappedIllegalArgumentException("Invalid arguments length ${argList.size} for $key.bytes")
}
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun bytesRhino(
source: Any? = null, /* Double */
fromUnit: Any? = null, /* String */
funcSignature: List<String> = emptyList(),
): Any {
val signature = when {
funcSignature.isNotEmpty() -> "$key.bytes(${funcSignature.joinToString(", ")})"
else -> "$key.bytes()"
}
return CoreBytes.numberRhino(
source = source,
fromUnit = fromUnit,
toUnit = "B",
fractionDigits = 0,
signature = signature,
)
}
private fun List<String>.forBytes(
source: Any? = null, /* Double */
fromUnit: Any? = null, /* String */
) = bytesRhino(source, fromUnit, this)
private fun getTimeUnitSourceObject(unit: Any?): TimeUnit = when (unit) {
is TimeUnit -> unit
is String -> timeUnitRex.entries.find { it.value.matches(unit) }?.let {

View File

@@ -2,11 +2,8 @@ package org.autojs.autojs.runtime.api.augment.util
import org.autojs.autojs.annotation.RhinoFunctionBody
import org.autojs.autojs.core.automator.UiObjectCollection
import org.autojs.autojs.extension.AnyExtensions.isJsBoolean
import org.autojs.autojs.extension.AnyExtensions.isJsNonNullObject
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
import org.autojs.autojs.extension.AnyExtensions.isJsNumber
import org.autojs.autojs.extension.AnyExtensions.isJsString
import org.autojs.autojs.extension.AnyExtensions.jsBrief
import org.autojs.autojs.extension.ArrayExtensions.toNativeObject
import org.autojs.autojs.extension.FlexibleArray.Companion.component1
@@ -261,7 +258,13 @@ object Inspect : Augmentable(), Invokable {
private fun formatValue(ctx: Ctx, `val`: Any?, recurseTimes: Int?): String {
var value = `val`
while (value is Wrapper) value = value.unwrap()
while (value is Wrapper) {
val unwrapped = value.unwrap()
if (unwrapped is Number) {
return callToStringFunction(value as Scriptable)
}
value = unwrapped
}
if (value is TopLevel) return "[object ${value.className}]"
@@ -579,7 +582,7 @@ object Inspect : Augmentable(), Invokable {
// For some reason, typeof null is "object", so special case here.
ctx.stylize("null", "null")
}
value.isJsString() -> {
value is String -> {
val content = Context.toString(js_json_stringify(value))
.removeSurrounding("\"")
.replace(Regex("'"), "\\\'")
@@ -590,16 +593,15 @@ object Inspect : Augmentable(), Invokable {
// ! to make a string quoted by single quotation marks.
// ! zh-CN:
// ! 我认为将字符串用单引号包裹起来并不是一个好主意.
// ! PS: 我认为不应该翻译为 "我不认为...". :)
// !
// # ctx.stylize("'$content'", "string")
ctx.stylize(content, "string")
}
value.isJsNumber() -> {
value is Number -> {
ctx.stylize(value.toString(), "number")
}
value.isJsBoolean() -> {
value is Boolean -> {
ctx.stylize(Context.toString(value), "boolean")
}
else -> null

View File

@@ -51,6 +51,7 @@ import org.mozilla.javascript.Wrapper
import org.mozilla.javascript.json.JsonParser
import java.io.Serializable
import java.lang.reflect.InvocationTargetException
import java.math.BigInteger
import kotlin.math.floor
import kotlin.math.roundToInt
import kotlin.math.roundToLong
@@ -66,6 +67,23 @@ object RhinoUtils {
const val DEFAULT_CALLER = 0x03
const val DEFAULT_CONSTRUCTOR = 0x04
/** [Long]: 2^53 - 1. */
const val MAX_SAFE_INT_IEEE754_L = 9_007_199_254_740_991L
/** [Long]: -(2^53 - 1). */
const val MIN_SAFE_INT_IEEE754_L = -9_007_199_254_740_991L
/** [Double]: 2^53 - 1. */
const val MAX_SAFE_INT_IEEE754_D = 9_007_199_254_740_991.0
/** [Double]: -(2^53 - 1). */
const val MIN_SAFE_INT_IEEE754_D = -9_007_199_254_740_991.0
/** [java.math.BigDecimal]: 2^53 - 1. */
val MAX_SAFE_INT_IEEE754_BD = MAX_SAFE_INT_IEEE754_D.toBigDecimal()
/** [java.math.BigDecimal]: -(2^53 - 1). */
val MIN_SAFE_INT_IEEE754_BD = MIN_SAFE_INT_IEEE754_D.toBigDecimal()
/** [java.math.BigInteger]: 2^53 - 1. */
val MAX_SAFE_INT_IEEE754_BI = MAX_SAFE_INT_IEEE754_L.toBigInteger()
/** [java.math.BigInteger]: -(2^53 - 1). */
val MIN_SAFE_INT_IEEE754_BI = MIN_SAFE_INT_IEEE754_L.toBigInteger()
private val TAG = RhinoUtils::class.java.simpleName
@JvmStatic
@@ -274,8 +292,8 @@ object RhinoUtils {
@JvmStatic
fun unwrap(o: Any?): Any? = when (o) {
is String -> o
is ConsString -> o.toString()
is String, is ConsString -> Context.toString(o)
is BigInteger -> o
is Number -> Context.toNumber(o)
is Boolean -> Context.toBoolean(o)
is Wrapper -> unwrap(o.unwrap())