--- /dev/null
+package net.pterodactylus.sone.template
+
+import net.pterodactylus.util.template.*
+import java.time.*
+
+class DurationFormatFilter : Filter {
+
+ override fun format(templateContext: TemplateContext?, data: Any?, parameters: Map<String, Any?>?): Any? {
+ if (data is Number) {
+ val scale = parameters?.get("scale")
+ val duration = when (scale) {
+ "ms" -> Duration.ofSeconds(data.toLong() / 1_000, (data.toDouble() * 1_000_000 % 1_000_000_000).toLong())
+ "μs" -> Duration.ofSeconds(data.toLong() / 1_000_000, (data.toDouble() * 1_000 % 1_000_000_000).toLong())
+ "ns" -> Duration.ofSeconds(data.toLong() / 1_000_000_000, data.toLong() % 1_000_000_000)
+ else -> Duration.ofSeconds(data.toLong(), (data.toDouble() * 1_000_000_000 % 1_000_000_000).toLong())
+ }
+ return FixedDuration.values()
+ .map { it to it.number(duration) }
+ .firstOrNull { it.second >= 1 }
+ ?.let { "${"%.1f".format(it.second)}${it.first.symbol}" }
+ ?: "0s"
+ }
+ return data
+ }
+
+}
+
+@Suppress("unused")
+private enum class FixedDuration {
+
+ WEEKS {
+ override fun number(duration: Duration) = DAYS.number(duration) / 7.0
+ override val symbol = "w"
+ },
+ DAYS {
+ override fun number(duration: Duration) = HOURS.number(duration) / 24
+ override val symbol = "d"
+ },
+ HOURS {
+ override fun number(duration: Duration) = MINUTES.number(duration) / 60
+ override val symbol = "h"
+ },
+ MINUTES {
+ override fun number(duration: Duration) = SECONDS.number(duration) / 60
+ override val symbol = "m"
+ },
+ SECONDS {
+ override fun number(duration: Duration) = duration.seconds + duration.nano / 1_000_000_000.0
+ override val symbol = "s"
+ },
+ MILLIS {
+ override fun number(duration: Duration) = duration.nano / 1_000_000.0
+ override val symbol = "ms"
+ },
+ MICROS {
+ override fun number(duration: Duration) = duration.nano / 1_1000.0
+ override val symbol = "μs"
+ },
+ NANOS {
+ override fun number(duration: Duration) = duration.nano.toDouble()
+ override val symbol = "ns"
+ };
+
+ abstract fun number(duration: Duration): Double
+ abstract val symbol: String
+
+}
addFilter("reparse", ReparseFilter())
addFilter("unknown", unknownDateFilter)
addFilter("format", FormatFilter())
+ addFilter("duration", DurationFormatFilter())
addFilter("sort", CollectionSortFilter())
addFilter("image-link", imageLinkFilter)
addFilter("replyGroup", ReplyGroupFilter())
<tr>
<td><%= Page.Metrics.SoneInsertDuration.Title|l10n|html></td>
<td class="numeric"><% soneInsertDurationCount|html></td>
- <td class="numeric"><% soneInsertDurationMin|html>μs</td>
- <td class="numeric"><% soneInsertDurationMax|html>μs</td>
- <td class="numeric"><% soneInsertDurationMean|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneInsertDurationMedian|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneInsertDurationPercentile75|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneInsertDurationPercentile95|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneInsertDurationPercentile98|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneInsertDurationPercentile99|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneInsertDurationPercentile999|format format=='%.0f'|html>μs</td>
+ <td class="numeric"><% soneInsertDurationMin|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationMax|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationMean|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationMedian|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationPercentile75|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationPercentile95|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationPercentile98|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationPercentile99|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneInsertDurationPercentile999|duration scale=="μs"|html></td>
</tr>
<tr>
<td><%= Page.Metrics.SoneParsingDuration.Title|l10n|html></td>
<td class="numeric"><% soneParsingDurationCount|html></td>
- <td class="numeric"><% soneParsingDurationMin|html>μs</td>
- <td class="numeric"><% soneParsingDurationMax|html>μs</td>
- <td class="numeric"><% soneParsingDurationMean|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneParsingDurationMedian|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneParsingDurationPercentile75|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneParsingDurationPercentile95|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneParsingDurationPercentile98|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneParsingDurationPercentile99|format format=='%.0f'|html>μs</td>
- <td class="numeric"><% soneParsingDurationPercentile999|format format=='%.0f'|html>μs</td>
+ <td class="numeric"><% soneParsingDurationMin|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationMax|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationMean|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationMedian|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationPercentile75|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationPercentile95|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationPercentile98|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationPercentile99|duration scale=="μs"|html></td>
+ <td class="numeric"><% soneParsingDurationPercentile999|duration scale=="μs"|html></td>
</tr>
</tbody>
</table>
--- /dev/null
+/**
+ * Sone - DurationFormatFilterTest.kt - Copyright © 2019 David ‘Bombe’ Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.template
+
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+class DurationFormatFilterTest {
+
+ private val filter = DurationFormatFilter()
+
+ @Test
+ fun `random object is returned as it is`() {
+ val randomObject = Any()
+ assertThat(filter.format(null, randomObject, emptyMap()), sameInstance(randomObject))
+ }
+
+ @Test
+ fun `integer 0 is rendered as “0s”`() {
+ verifyDuration(0, "0s")
+ }
+
+ @Test
+ fun `long 0 is rendered as “0s”`() {
+ verifyDuration(0L, "0s")
+ }
+
+ @Test
+ fun `12 is rendered as “12_0s”`() {
+ verifyDuration(12, "12.0s")
+ }
+
+ @Test
+ fun `123 is rendered as “2_1m”`() {
+ verifyDuration(123, "2.1m")
+ }
+
+ @Test
+ fun `12345 is rendered as “3_4h”`() {
+ verifyDuration(12345, "3.4h")
+ }
+
+ @Test
+ fun `123456 is rendered as “1_4d”`() {
+ verifyDuration(123456, "1.4d")
+ }
+
+ @Test
+ fun `1234567 is rendered as “2_0w”`() {
+ verifyDuration(1234567, "2.0w")
+ }
+
+ @Test
+ fun `123456789 with scale ms is rendered as “1_4d”`() {
+ verifyDuration(123456789, "1.4d", "ms")
+ }
+
+ @Test
+ fun `123456789 with scale μs is rendered as “2_1m”`() {
+ verifyDuration(123456789, "2.1m", "μs")
+ }
+
+ @Test
+ fun `123456789 with scale ns is rendered as “123_5ms”`() {
+ verifyDuration(123456789, "123.5ms", "ns")
+ }
+
+ private fun verifyDuration(value: Any, expectedRendering: String, scale: String? = null) {
+ assertThat(filter.format(null, value, scale?.let { mapOf("scale" to scale) } ?: emptyMap()), equalTo<Any>(expectedRendering))
+ }
+
+}
import org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.*
import org.junit.*
+import kotlin.test.Test
class WebInterfaceModuleTest {
}
@Test
+ fun `template context contains duration format filter`() {
+ verifyFilter<DurationFormatFilter>("duration")
+ }
+
+ @Test
fun `template context contains collection sort filter`() {
verifyFilter<CollectionSortFilter>("sort")
}