diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/markdown/MarkdownFormatter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/markdown/MarkdownFormatter.kt index f884f0870..eec454d95 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/markdown/MarkdownFormatter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/markdown/MarkdownFormatter.kt @@ -25,7 +25,6 @@ import com.itangcent.intellij.context.ActionContext import com.itangcent.intellij.extend.toPrettyString import com.itangcent.intellij.util.ActionUtils import com.itangcent.intellij.util.forEachValid -import java.util.* /** * format [com.itangcent.common.model.Doc] to `markdown`. @@ -465,14 +464,18 @@ private class SimpleObjectFormatter(handle: (String) -> Unit) : AbstractObjectFo addBodyProperty(deep, name, "array", desc) if (obj.size > 0) { - writeBody(obj[0], "", "", deep + 1) + obj.forEach { + writeBody(it, "", "", deep + 1) + } } else { writeBody(null, "", "", deep + 1) } - } else if (obj is List<*>) { + } else if (obj is Collection<*>) { addBodyProperty(deep, name, "array", desc) if (obj.size > 0) { - writeBody(obj[0], "", "", deep + 1) + obj.forEach { + writeBody(it, "", "", deep + 1) + } } else { writeBody(null, "", "", deep + 1) } @@ -480,9 +483,9 @@ private class SimpleObjectFormatter(handle: (String) -> Unit) : AbstractObjectFo if (deep > 0) { addBodyProperty(deep, name, "object", desc) } - var comment: HashMap? = null + var comment: Map? = null try { - comment = obj[Attrs.COMMENT_ATTR] as HashMap? + comment = obj[Attrs.COMMENT_ATTR] as Map? } catch (e: Throwable) { } obj.forEachValid { k, v -> @@ -528,14 +531,18 @@ private class UltimateObjectFormatter(handle: (String) -> Unit) : AbstractObject if (obj is Array<*>) { addBodyProperty(deep, name, "array", required, default, desc) if (obj.size > 0) { - writeBody(obj[0], "", null, null, "", deep + 1) + obj.forEach { + writeBody(it, "", null, null, "", deep + 1) + } } else { writeBody(null, "", null, null, "", deep + 1) } - } else if (obj is List<*>) { + } else if (obj is Collection<*>) { addBodyProperty(deep, name, "array", desc) if (obj.size > 0) { - writeBody(obj[0], "", null, null, "", deep + 1) + obj.forEach { + writeBody(it, "", null, null, "", deep + 1) + } } else { writeBody(null, "", null, null, "", deep + 1) } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatter.kt index 2a186c55a..a9204ef7c 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/PostmanFormatter.kt @@ -3,6 +3,8 @@ package com.itangcent.idea.plugin.api.export.postman import com.google.inject.Inject import com.google.inject.Singleton import com.intellij.psi.PsiClass +import com.itangcent.common.constant.Attrs +import com.itangcent.common.kit.KVUtils import com.itangcent.common.model.Request import com.itangcent.common.model.URL import com.itangcent.common.model.getContentType @@ -21,6 +23,8 @@ import com.itangcent.idea.utils.ModuleHelper import com.itangcent.intellij.config.rule.RuleComputer import com.itangcent.intellij.context.ActionContext import com.itangcent.intellij.util.ActionUtils +import com.itangcent.intellij.util.forEachValid +import com.itangcent.intellij.util.validSize import org.apache.commons.lang3.RandomUtils import java.util.* import kotlin.collections.ArrayList @@ -215,7 +219,7 @@ open class PostmanFormatter { if (request.body != null) { body[MODE] = "raw" - body["raw"] = RequestUtils.parseRawBody(request.body!!) + body["raw"] = getBodyFormatter().format(request.body) body["options"] = KV.by("raw", KV.by("language", "json")) } @@ -293,7 +297,7 @@ open class PostmanFormatter { responseInfo["responseTime"] = RandomUtils.nextInt(10, 100) - responseInfo["body"] = response.body?.let { RequestUtils.parseRawBody(it) } + responseInfo["body"] = getBodyFormatter().format(response.body) responses.add(responseInfo) } @@ -591,6 +595,15 @@ open class PostmanFormatter { } } + private fun getBodyFormatter(): BodyFormatter { + val useJson5 = settingBinder!!.read().useJson5 + return if (useJson5) { + Json5BodyFormatter() + } else { + SimpleJsonBodyFormatter() + } + } + companion object { const val NULL_RESOURCE = "unknown" @@ -598,6 +611,175 @@ open class PostmanFormatter { } } +private interface BodyFormatter { + fun format(obj: Any?): String +} + +private class SimpleJsonBodyFormatter : BodyFormatter { + override fun format(obj: Any?): String { + return obj?.let { RequestUtils.parseRawBody(it) } ?: "" + } +} + +/** + * @see {@link https://github.com/json5/json5} + * @see {@link https://json5.org/} + */ +@Suppress("UNCHECKED_CAST") +private class Json5BodyFormatter : BodyFormatter { + override fun format(obj: Any?): String { + val sb = StringBuilder() + format(obj, 0, true, null, sb) + return sb.toString() + } + + fun format(obj: Any?, deep: Int, end: Boolean, desc: String?, sb: StringBuilder) { + when (obj) { + null -> { + sb.append("null") + sb.appendEnd(end) + sb.appendEndLineComment(desc) + } + is Array<*> -> { + if (obj.isEmpty()) { + sb.append("[]") + sb.appendEnd(end) + sb.appendEndLineComment(desc) + return + } + sb.append("[") + sb.appendEndLineComment(desc) + val endCounter = EndCounter(obj.size) + obj.forEach { + sb.nextLine(deep + 1) + format(it, deep + 1, endCounter.end(), null, sb) + } + sb.nextLine(deep) + sb.append("]") + sb.appendEnd(end) + } + is Collection<*> -> { + if (obj.isEmpty()) { + sb.append("[]") + sb.appendEnd(end) + sb.appendEndLineComment(desc) + return + } + sb.append("[") + sb.appendEndLineComment(desc) + val endCounter = EndCounter(obj.size) + obj.forEach { + sb.nextLine(deep + 1) + format(it, deep + 1, endCounter.end(), null, sb) + } + sb.nextLine(deep) + sb.append("]") + sb.appendEnd(end) + } + is Map<*, *> -> { + if (obj.isEmpty()) { + sb.append("{}") + sb.appendEnd(end) + sb.appendEndLineComment(desc) + return + } + var comment: Map? = null + try { + comment = obj[Attrs.COMMENT_ATTR] as Map? + } catch (e: Throwable) { + } + sb.append("{") + sb.appendEndLineComment(desc) + val endCounter = EndCounter(obj.validSize()) + obj.forEachValid { k, v -> + val propertyDesc: String? = KVUtils.getUltimateComment(comment, k) + sb.nextLine(deep + 1) + format(k.toString(), v, deep + 1, propertyDesc ?: "", endCounter.end(), sb) + } + sb.nextLine(deep) + sb.append("}") + sb.appendEnd(end) + } + is String -> { + sb.appendString(obj) + sb.appendEnd(end) + sb.appendEndLineComment(desc) + } + else -> { + sb.append(obj) + sb.appendEnd(end) + sb.appendEndLineComment(desc) + } + } + } + + fun format(name: String, obj: Any?, deep: Int, desc: String?, end: Boolean, sb: StringBuilder) { + if (desc.isNullOrBlank()) { + sb.appendString(name) + sb.append(": ") + format(obj, deep, end, desc, sb) + return + } + val lines = desc.lines() + if (lines.size == 1) { + sb.appendString(name) + sb.append(": ") + format(obj, deep, end, desc, sb) + return + } else { + sb.appendBlockComment(lines, deep) + sb.appendString(name) + sb.append(": ") + format(obj, deep, end, null, sb) + return + } + } + + private fun StringBuilder.appendString(key: String) { + this.append('"') + this.append(key) + this.append('"') + } + + private fun StringBuilder.appendEnd(end: Boolean) { + if (!end) { + this.append(',') + } + } + + private fun StringBuilder.appendBlockComment(descs: List, deep: Int) { + this.append("/**") + this.appendln() + descs.forEach { + this.append(TAB.repeat(deep)) + this.append(" * ") + this.appendln(it) + } + this.append(TAB.repeat(deep)) + this.append(" */") + this.appendln() + this.append(TAB.repeat(deep)) + } + + private fun StringBuilder.appendEndLineComment(desc: String?) { + if (desc.notNullOrBlank()) { + this.append("//") + this.append(desc) + } + } + + private fun StringBuilder.nextLine(deep: Int) { + this.appendln() + this.append(TAB.repeat(deep)) + } +} + +private class EndCounter(val size: Int) { + private var i = 0 + fun end(): Boolean { + return ++i == size + } +} private const val NAME = "name" private const val KEY = "key" @@ -607,3 +789,19 @@ private const val MODE = "mode" private const val DESCRIPTION = "description" private const val EVENT = "event" private const val ITEM = "item" +private const val TAB = " " + + +fun main() { + val json5BodyFormatter = Json5BodyFormatter() + println(json5BodyFormatter.format("aa")) + println(json5BodyFormatter.format(KV.by("aa", "bb"))) + println(json5BodyFormatter.format(KV.by("aa", listOf("bb", KV.by("aa", "bb"))))) + println(json5BodyFormatter.format( + KV.by("aa", listOf("bb", KV.by("aa", "bb"))) + .set("bb", listOf("cc", KV.by("aa", "bb"))) + .set(Attrs.COMMENT_ATTR, KV.by("aa", "string - a") + .set("bb", "bbb\nbbb\nbbb") + ) + )) +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.form b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.form index ef24a49b2..5afeffa85 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.form +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.form @@ -3,7 +3,7 @@ - + @@ -190,7 +190,7 @@ - + @@ -289,6 +289,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.kt index 6f5ddd54c..1d907851d 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/EasyApiSettingGUI.kt @@ -50,6 +50,8 @@ class EasyApiSettingGUI { private var autoMergeScriptCheckBox: JCheckBox? = null + private var useJson5CheckBox: JCheckBox? = null + //endregion //region general----------------------------------------------------- @@ -144,6 +146,9 @@ class EasyApiSettingGUI { autoComputer.bind(autoMergeScriptCheckBox!!) .mutual(this, "settings.autoMergeScript") + autoComputer.bind(useJson5CheckBox!!) + .mutual(this, "settings.useJson5") + autoComputer.bind(this.globalCacheSizeLabel!!) .with(this::globalCacheSize) .eval { it } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/Settings.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/Settings.kt index abc5fdb73..79b3a7daf 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/Settings.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/Settings.kt @@ -18,6 +18,8 @@ class Settings { var autoMergeScript: Boolean = false + var useJson5: Boolean = true + //intelligent var formExpanded: Boolean = true @@ -59,6 +61,7 @@ class Settings { newSetting.postmanToken = this.postmanToken newSetting.wrapCollection = this.wrapCollection newSetting.autoMergeScript = this.autoMergeScript + newSetting.useJson5 = this.useJson5 newSetting.pullNewestDataBefore = this.pullNewestDataBefore newSetting.methodDocEnable = this.methodDocEnable newSetting.formExpanded = this.formExpanded @@ -90,6 +93,7 @@ class Settings { if (postmanToken != other.postmanToken) return false if (wrapCollection != other.wrapCollection) return false if (autoMergeScript != other.autoMergeScript) return false + if (useJson5 != other.useJson5) return false if (formExpanded != other.formExpanded) return false if (readGetter != other.readGetter) return false if (inferEnable != other.inferEnable) return false @@ -115,6 +119,7 @@ class Settings { result = 31 * result + (postmanToken?.hashCode() ?: 0) result = 31 * result + wrapCollection.hashCode() result = 31 * result + autoMergeScript.hashCode() + result = 31 * result + useJson5.hashCode() result = 31 * result + formExpanded.hashCode() result = 31 * result + readGetter.hashCode() result = 31 * result + inferEnable.hashCode() diff --git a/idea-plugin/src/main/kotlin/com/itangcent/intellij/util/KVKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/intellij/util/KVKit.kt index ba4038058..f4a4f791a 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/intellij/util/KVKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/intellij/util/KVKit.kt @@ -28,7 +28,7 @@ fun KV.forEachValid(action: (String, V) -> Unit) { @Suppress("UNCHECKED_CAST") fun Map.forEachValid(action: (K, V) -> Unit) { - this.forEach { k, v -> + this.forEach { (k, v) -> if (k == null) { return@forEach } else if (k is String) { @@ -46,6 +46,10 @@ fun Map.forEachValid(action: (K, V) -> Unit) { } } +fun Map.validSize(): Int { + return this.keys.count { it !is String || !it.startsWith('@') } +} + fun Map<*, *>.flatValid(consumer: FieldConsumer) { this.forEachValid { key, value -> if (key == null) return@forEachValid