Rewrite range operations in new DSL
This commit is contained in:
@@ -24,9 +24,9 @@ fun main(args: Array<String>) {
|
||||
Generators,
|
||||
StringJoinOps,
|
||||
SequenceOps,
|
||||
// RangeOps,
|
||||
RangeOps,
|
||||
Numeric,
|
||||
// ComparableOps,
|
||||
ComparableOps,
|
||||
// CommonArrays,
|
||||
// PlatformSpecialized,
|
||||
// PlatformSpecializedJS,
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package templates
|
||||
|
||||
import templates.Family.*
|
||||
|
||||
fun comparables(): List<GenericFunction> {
|
||||
val templates = arrayListOf<GenericFunction>()
|
||||
object ComparableOps : TemplateGroupBase() {
|
||||
|
||||
templates add f("coerceAtLeast(minimumValue: SELF)") {
|
||||
private val numericPrimitives = PrimitiveType.numericPrimitives.sortedBy { it.capacity }.toSet()
|
||||
private val intPrimitives = setOf(PrimitiveType.Int, PrimitiveType.Long)
|
||||
private val shortIntPrimitives = setOf(PrimitiveType.Byte, PrimitiveType.Short)
|
||||
|
||||
val f_coerceAtLeast = fn("coerceAtLeast(minimumValue: SELF)") {
|
||||
include(Generic)
|
||||
include(Primitives, numericPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Ranges)
|
||||
only(Primitives, Generic)
|
||||
only(numericPrimitives)
|
||||
returns("SELF")
|
||||
typeParam("T: Comparable<T>")
|
||||
doc {
|
||||
@@ -23,13 +43,13 @@ fun comparables(): List<GenericFunction> {
|
||||
return if (this < minimumValue) minimumValue else this
|
||||
"""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
templates add f("coerceAtMost(maximumValue: SELF)") {
|
||||
val f_coerceAtMost = fn("coerceAtMost(maximumValue: SELF)") {
|
||||
include(Generic)
|
||||
include(Primitives, numericPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Ranges)
|
||||
only(Primitives, Generic)
|
||||
only(numericPrimitives)
|
||||
returns("SELF")
|
||||
typeParam("T: Comparable<T>")
|
||||
doc {
|
||||
@@ -46,10 +66,11 @@ fun comparables(): List<GenericFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("coerceIn(range: ClosedRange<T>)") {
|
||||
val f_coerceIn_range_primitive = fn("coerceIn(range: ClosedRange<T>)") {
|
||||
include(Generic)
|
||||
include(Primitives, intPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Ranges)
|
||||
only(Primitives, Generic)
|
||||
only(PrimitiveType.Int, PrimitiveType.Long)
|
||||
returns("SELF")
|
||||
typeParam("T: Comparable<T>")
|
||||
doc {
|
||||
@@ -87,9 +108,10 @@ fun comparables(): List<GenericFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("coerceIn(range: ClosedFloatingPointRange<T>)") {
|
||||
val f_coerceIn_fpRange = fn("coerceIn(range: ClosedFloatingPointRange<T>)") {
|
||||
include(Generic)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Ranges)
|
||||
only(Generic)
|
||||
since("1.1")
|
||||
returns("SELF")
|
||||
typeParam("T: Comparable<T>")
|
||||
@@ -115,15 +137,16 @@ fun comparables(): List<GenericFunction> {
|
||||
}
|
||||
|
||||
|
||||
templates add f("minOf(a: T, b: T)") {
|
||||
val f_minOf_2 = fn("minOf(a: T, b: T)") {
|
||||
include(Generic)
|
||||
include(Primitives, numericPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Primitives, Generic)
|
||||
only(numericPrimitives)
|
||||
since("1.1")
|
||||
typeParam("T: Comparable<T>")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
inline(Primitives) { Inline.Only }
|
||||
receiver("")
|
||||
specialFor(Primitives) { inlineOnly() }
|
||||
doc {
|
||||
"""
|
||||
Returns the smaller of two values.
|
||||
@@ -131,50 +154,56 @@ fun comparables(): List<GenericFunction> {
|
||||
"""
|
||||
}
|
||||
// TODO: Add a note about NaN propagation for floats.
|
||||
doc(Primitives) {
|
||||
"""Returns the smaller of two values."""
|
||||
}
|
||||
bodyForTypes(Primitives, PrimitiveType.Byte, PrimitiveType.Short) { p ->
|
||||
"return Math.min(a.toInt(), b.toInt()).to$p()"
|
||||
}
|
||||
// TODO: custom body for JS minOf(Long, Long)
|
||||
body(Primitives) {
|
||||
"return Math.min(a, b)"
|
||||
specialFor(Primitives) {
|
||||
doc {
|
||||
"""Returns the smaller of two values."""
|
||||
}
|
||||
// TODO: custom body for JS minOf(Long, Long)
|
||||
body {
|
||||
"return Math.min(a, b)"
|
||||
}
|
||||
if (primitive in shortIntPrimitives) {
|
||||
body { "return Math.min(a.toInt(), b.toInt()).to$primitive()" }
|
||||
}
|
||||
}
|
||||
body(Generic) {
|
||||
"return if (a <= b) a else b"
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("minOf(a: T, b: T, c: T)") {
|
||||
val f_minOf = fn("minOf(a: T, b: T, c: T)") {
|
||||
include(Generic)
|
||||
include(Primitives, numericPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Primitives, Generic)
|
||||
only(numericPrimitives)
|
||||
since("1.1")
|
||||
typeParam("T: Comparable<T>")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
inline(Primitives) { Inline.Only }
|
||||
receiver("")
|
||||
specialFor(Primitives) { inlineOnly() }
|
||||
// TODO: Add a note about NaN propagation for floats.
|
||||
doc {
|
||||
"""
|
||||
Returns the smaller of three values.
|
||||
"""
|
||||
}
|
||||
bodyForTypes(Primitives, PrimitiveType.Byte, PrimitiveType.Short) { p ->
|
||||
"return Math.min(a.toInt(), Math.min(b.toInt(), c.toInt())).to$p()"
|
||||
}
|
||||
body {
|
||||
"return minOf(a, minOf(b, c))"
|
||||
}
|
||||
specialFor(Primitives) {
|
||||
if (primitive in shortIntPrimitives) {
|
||||
body { "return Math.min(a.toInt(), Math.min(b.toInt(), c.toInt())).to$primitive()" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("minOf(a: T, b: T, comparator: Comparator<in T>)") {
|
||||
val f_minOf_2_comparator = fn("minOf(a: T, b: T, comparator: Comparator<in T>)") {
|
||||
include(Generic)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Generic)
|
||||
since("1.1")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
receiver("")
|
||||
doc {
|
||||
"""
|
||||
Returns the smaller of two values according to the order specified by the given [comparator].
|
||||
@@ -186,12 +215,13 @@ fun comparables(): List<GenericFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("minOf(a: T, b: T, c: T, comparator: Comparator<in T>)") {
|
||||
val f_minOf_3_comparator = fn("minOf(a: T, b: T, c: T, comparator: Comparator<in T>)") {
|
||||
include(Generic)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Generic)
|
||||
since("1.1")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
receiver("")
|
||||
doc {
|
||||
"""
|
||||
Returns the smaller of three values according to the order specified by the given [comparator].
|
||||
@@ -201,16 +231,17 @@ fun comparables(): List<GenericFunction> {
|
||||
"return minOf(a, minOf(b, c, comparator), comparator)"
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("maxOf(a: T, b: T)") {
|
||||
|
||||
val f_maxOf_2 = fn("maxOf(a: T, b: T)") {
|
||||
include(Generic)
|
||||
include(Primitives, numericPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Primitives, Generic)
|
||||
only(numericPrimitives)
|
||||
since("1.1")
|
||||
typeParam("T: Comparable<T>")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
inline(Primitives) { Inline.Only }
|
||||
receiver("")
|
||||
specialFor(Primitives) { inlineOnly() }
|
||||
doc {
|
||||
"""
|
||||
Returns the greater of two values.
|
||||
@@ -218,49 +249,55 @@ fun comparables(): List<GenericFunction> {
|
||||
"""
|
||||
}
|
||||
// TODO: Add a note about NaN propagation for floats.
|
||||
doc(Primitives) {
|
||||
"""Returns the greater of two values."""
|
||||
}
|
||||
bodyForTypes(Primitives, PrimitiveType.Byte, PrimitiveType.Short) { p ->
|
||||
"return Math.max(a.toInt(), b.toInt()).to$p()"
|
||||
}
|
||||
body(Primitives) {
|
||||
"return Math.max(a, b)"
|
||||
specialFor(Primitives) {
|
||||
doc {
|
||||
"""Returns the greater of two values."""
|
||||
}
|
||||
body {
|
||||
"return Math.max(a, b)"
|
||||
}
|
||||
if (primitive in shortIntPrimitives) {
|
||||
body { "return Math.max(a.toInt(), b.toInt()).to$primitive()" }
|
||||
}
|
||||
}
|
||||
body(Generic) {
|
||||
"return if (a >= b) a else b"
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("maxOf(a: T, b: T, c: T)") {
|
||||
val f_maxOf_3 = fn("maxOf(a: T, b: T, c: T)") {
|
||||
include(Generic)
|
||||
include(Primitives, numericPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Primitives, Generic)
|
||||
only(numericPrimitives)
|
||||
since("1.1")
|
||||
typeParam("T: Comparable<T>")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
inline(Primitives) { Inline.Only }
|
||||
receiver("")
|
||||
specialFor(Primitives) { inlineOnly() }
|
||||
// TODO: Add a note about NaN propagation for floats.
|
||||
doc {
|
||||
"""
|
||||
Returns the greater of three values.
|
||||
"""
|
||||
}
|
||||
bodyForTypes(Primitives, PrimitiveType.Byte, PrimitiveType.Short) { p ->
|
||||
"return Math.max(a.toInt(), Math.max(b.toInt(), c.toInt())).to$p()"
|
||||
}
|
||||
body {
|
||||
"return maxOf(a, maxOf(b, c))"
|
||||
}
|
||||
specialFor(Primitives) {
|
||||
if (primitive in shortIntPrimitives) {
|
||||
body { "return Math.max(a.toInt(), Math.max(b.toInt(), c.toInt())).to$primitive()" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("maxOf(a: T, b: T, comparator: Comparator<in T>)") {
|
||||
val f_maxOf_2_comparator = fn("maxOf(a: T, b: T, comparator: Comparator<in T>)") {
|
||||
include(Generic)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Generic)
|
||||
since("1.1")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
receiver("")
|
||||
doc {
|
||||
"""
|
||||
Returns the greater of two values according to the order specified by the given [comparator].
|
||||
@@ -272,12 +309,13 @@ fun comparables(): List<GenericFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("maxOf(a: T, b: T, c: T, comparator: Comparator<in T>)") {
|
||||
val f_maxOf_3_comparator = fn("maxOf(a: T, b: T, c: T, comparator: Comparator<in T>)") {
|
||||
include(Generic)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Comparisons)
|
||||
only(Generic)
|
||||
since("1.1")
|
||||
returns("T")
|
||||
customReceiver("")
|
||||
receiver("")
|
||||
doc {
|
||||
"""
|
||||
Returns the greater of three values according to the order specified by the given [comparator].
|
||||
@@ -289,11 +327,13 @@ fun comparables(): List<GenericFunction> {
|
||||
}
|
||||
|
||||
|
||||
templates add f("coerceIn(minimumValue: SELF, maximumValue: SELF)") {
|
||||
val f_coerceIn_min_max = fn("coerceIn(minimumValue: SELF, maximumValue: SELF)") {
|
||||
include(Generic)
|
||||
include(Primitives, numericPrimitives)
|
||||
} builder {
|
||||
sourceFile(SourceFile.Ranges)
|
||||
only(Primitives, Generic)
|
||||
only(numericPrimitives)
|
||||
customSignature(Generic) { "coerceIn(minimumValue: SELF?, maximumValue: SELF?)" }
|
||||
|
||||
specialFor(Generic) { signature("coerceIn(minimumValue: SELF?, maximumValue: SELF?)", notForSorting = true) }
|
||||
typeParam("T: Comparable<T>")
|
||||
returns("SELF")
|
||||
doc {
|
||||
@@ -326,6 +366,4 @@ fun comparables(): List<GenericFunction> {
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
return templates
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package templates
|
||||
|
||||
import templates.Family.*
|
||||
import templates.PrimitiveType.Companion.maxByCapacity
|
||||
|
||||
fun ranges(): List<GenericFunction> {
|
||||
val templates = arrayListOf<GenericFunction>()
|
||||
object RangeOps : TemplateGroupBase() {
|
||||
|
||||
val rangePrimitives = listOf(PrimitiveType.Int, PrimitiveType.Long, PrimitiveType.Char)
|
||||
fun rangeElementType(fromType: PrimitiveType, toType: PrimitiveType)
|
||||
private val rangePrimitives = setOf(PrimitiveType.Int, PrimitiveType.Long, PrimitiveType.Char)
|
||||
private fun rangeElementType(fromType: PrimitiveType, toType: PrimitiveType)
|
||||
= maxByCapacity(fromType, toType).let { if (it == PrimitiveType.Char) it else maxByCapacity(it, PrimitiveType.Int) }
|
||||
|
||||
fun <T> Collection<T>.permutations(): List<Pair<T, T>> = flatMap { a -> map { b -> a to b } }
|
||||
private fun <T> Collection<T>.permutations(): List<Pair<T, T>> = flatMap { a -> map { b -> a to b } }
|
||||
|
||||
templates add f("reversed()") {
|
||||
only(ProgressionsOfPrimitives)
|
||||
only(rangePrimitives)
|
||||
doc(ProgressionsOfPrimitives) { "Returns a progression that goes over the same range in the opposite direction with the same step." }
|
||||
private val numericPrimitives = PrimitiveType.numericPrimitives
|
||||
private val numericPermutations = numericPrimitives.permutations()
|
||||
private val primitivePermutations = numericPermutations + (PrimitiveType.Char to PrimitiveType.Char)
|
||||
private val integralPermutations = primitivePermutations.filter { it.first.isIntegral() && it.second.isIntegral() }
|
||||
|
||||
|
||||
val f_reversed = fn("reversed()") {
|
||||
include(ProgressionsOfPrimitives, rangePrimitives)
|
||||
} builder {
|
||||
doc { "Returns a progression that goes over the same range in the opposite direction with the same step." }
|
||||
returns("TProgression")
|
||||
body(ProgressionsOfPrimitives) {
|
||||
body {
|
||||
"return TProgression.fromClosedRange(last, first, -step)"
|
||||
}
|
||||
}
|
||||
|
||||
templates add f("step(step: SUM)") {
|
||||
val f_step = fn("step(step: SUM)") {
|
||||
include(ProgressionsOfPrimitives, rangePrimitives)
|
||||
} builder {
|
||||
infix(true)
|
||||
|
||||
only(ProgressionsOfPrimitives)
|
||||
only(rangePrimitives)
|
||||
doc(ProgressionsOfPrimitives) { "Returns a progression that goes over the same range with the given step." }
|
||||
doc { "Returns a progression that goes over the same range with the given step." }
|
||||
returns("TProgression")
|
||||
body(ProgressionsOfPrimitives) {
|
||||
body {
|
||||
"""
|
||||
checkStepIsPositive(step > 0, step)
|
||||
return TProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
|
||||
@@ -37,14 +57,16 @@ fun ranges(): List<GenericFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
fun downTo(fromType: PrimitiveType, toType: PrimitiveType) = f("downTo(to: $toType)") {
|
||||
infix(true)
|
||||
|
||||
val f_downTo = fn("downTo(to: Primitive)").byTwoPrimitives {
|
||||
include(Primitives, integralPermutations)
|
||||
} builderWith { (fromType, toType) ->
|
||||
sourceFile(SourceFile.Ranges)
|
||||
only(Primitives)
|
||||
only(fromType)
|
||||
|
||||
val elementType = rangeElementType(fromType, toType)
|
||||
val progressionType = elementType.name + "Progression"
|
||||
|
||||
infix()
|
||||
signature("downTo(to: $toType)")
|
||||
returns(progressionType)
|
||||
|
||||
doc {
|
||||
@@ -55,6 +77,7 @@ fun ranges(): List<GenericFunction> {
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
val fromExpr = if (elementType == fromType) "this" else "this.to$elementType()"
|
||||
val toExpr = if (elementType == toType) "to" else "to.to$elementType()"
|
||||
val incrementExpr = when (elementType) {
|
||||
@@ -64,22 +87,20 @@ fun ranges(): List<GenericFunction> {
|
||||
else -> "-1"
|
||||
}
|
||||
|
||||
body { "return $progressionType.fromClosedRange($fromExpr, $toExpr, $incrementExpr)" }
|
||||
body {
|
||||
"return $progressionType.fromClosedRange($fromExpr, $toExpr, $incrementExpr)"
|
||||
}
|
||||
}
|
||||
|
||||
val numericPrimitives = PrimitiveType.numericPrimitives
|
||||
val numericPermutations = numericPrimitives.permutations()
|
||||
val primitivePermutations = numericPermutations + (PrimitiveType.Char to PrimitiveType.Char)
|
||||
val integralPermutations = primitivePermutations.filter { it.first.isIntegral() && it.second.isIntegral() }
|
||||
|
||||
templates addAll integralPermutations.map { downTo(it.first, it.second) }
|
||||
|
||||
fun until(fromType: PrimitiveType, toType: PrimitiveType) = f("until(to: $toType)") {
|
||||
infix(true)
|
||||
|
||||
val f_until = fn("until(to: Primitive)").byTwoPrimitives {
|
||||
include(Primitives, integralPermutations)
|
||||
} builderWith { (fromType, toType) ->
|
||||
sourceFile(SourceFile.Ranges)
|
||||
only(Primitives)
|
||||
only(fromType)
|
||||
|
||||
infix()
|
||||
signature("until(to: $toType)")
|
||||
|
||||
val elementType = rangeElementType(fromType, toType)
|
||||
val progressionType = elementType.name + "Range"
|
||||
returns(progressionType)
|
||||
@@ -116,14 +137,14 @@ fun ranges(): List<GenericFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
templates addAll integralPermutations.map { until(it.first, it.second) }
|
||||
|
||||
fun contains(rangeType: PrimitiveType, itemType: PrimitiveType) = f("contains(value: $itemType)") {
|
||||
operator(true)
|
||||
val f_contains = fn("contains(value: Primitive)").byTwoPrimitives {
|
||||
include(Ranges, numericPermutations)
|
||||
filter { _, (rangeType, itemType) -> rangeType != itemType }
|
||||
} builderWith { (rangeType, itemType) ->
|
||||
operator()
|
||||
signature("contains(value: $itemType)")
|
||||
|
||||
check(rangeType.isNumeric() == itemType.isNumeric()) { "Required rangeType and itemType both to be numeric or both not, got: $rangeType, $itemType" }
|
||||
only(Ranges)
|
||||
onlyPrimitives(Ranges, rangeType)
|
||||
platformName("${rangeType.name.decapitalize()}RangeContains")
|
||||
returns("Boolean")
|
||||
doc { "Checks if the specified [value] belongs to this range." }
|
||||
@@ -135,23 +156,18 @@ fun ranges(): List<GenericFunction> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
templates addAll numericPermutations.filter { it.first != it.second }.map { contains(it.first, it.second) }
|
||||
|
||||
fun narrowingExactOrNull(fromType: PrimitiveType, toType: PrimitiveType) = f("to${toType}ExactOrNull()") {
|
||||
val f_toPrimitiveExactOrNull = fn("to{}ExactOrNull()").byTwoPrimitives {
|
||||
include(Primitives, numericPermutations)
|
||||
filter { _, (fromType, toType) -> fromType.capacity > toType.capacity && toType.isIntegral() }
|
||||
} builderWith { (fromType, toType) ->
|
||||
check(toType.isIntegral())
|
||||
visibility("internal")
|
||||
sourceFile(SourceFile.Ranges)
|
||||
check(toType.isIntegral())
|
||||
|
||||
signature("to${toType}ExactOrNull()")
|
||||
returns("$toType?")
|
||||
only(Primitives)
|
||||
only(fromType)
|
||||
body {
|
||||
"return if (this in $toType.MIN_VALUE.to$fromType()..$toType.MAX_VALUE.to$fromType()) this.to$toType() else null"
|
||||
}
|
||||
}
|
||||
|
||||
templates addAll numericPermutations.filter { it.first.capacity > it.second.capacity && it.second.isIntegral() }.map { narrowingExactOrNull(it.first, it.second) }
|
||||
|
||||
return templates
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user