Skip to content

Commit 4c962ce

Browse files
committed
Define the public API representation of a format builder
The new class is the superinterface of all the receivers exposed via the format builder DSL.
1 parent dab9303 commit 4c962ce

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2019-2023 JetBrains s.r.o. and contributors.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.datetime.format
7+
8+
import kotlinx.datetime.internal.format.*
9+
10+
/**
11+
* Common functions for all format builders.
12+
*/
13+
public sealed interface DateTimeFormatBuilder {
14+
/**
15+
* A literal string.
16+
*
17+
* When formatting, the string is appended to the result as is,
18+
* and when parsing, the string is expected to be present in the input verbatim.
19+
*/
20+
public fun chars(value: String)
21+
}
22+
23+
/**
24+
* A format along with other ways to parse the same portion of the value.
25+
*
26+
* When parsing, first, [primaryFormat] is used; if parsing the whole string fails using that, the formats
27+
* from [alternativeFormats] are tried in order.
28+
*
29+
* When formatting, the [primaryFormat] is used to format the value, and [alternativeFormats] are ignored.
30+
*
31+
* Example:
32+
* ```
33+
* alternativeParsing(
34+
* { dayOfMonth(); char('-'); monthNumber() },
35+
* { monthNumber(); char(' '); dayOfMonth() },
36+
* ) { monthNumber(); char('/'); dayOfMonth() }
37+
* ```
38+
*
39+
* This will always format a date as `MM/DD`, but will also accept `DD-MM` and `MM DD`.
40+
*/
41+
@Suppress("UNCHECKED_CAST")
42+
public fun <T : DateTimeFormatBuilder> T.alternativeParsing(
43+
vararg alternativeFormats: T.() -> Unit,
44+
primaryFormat: T.() -> Unit
45+
): Unit = when (this) {
46+
is AbstractDateTimeFormatBuilder<*, *> ->
47+
appendAlternativeParsingImpl(
48+
*alternativeFormats as Array<out AbstractDateTimeFormatBuilder<*, *>.() -> Unit>,
49+
mainFormat = primaryFormat as (AbstractDateTimeFormatBuilder<*, *>.() -> Unit)
50+
)
51+
52+
else -> throw IllegalStateException("impossible")
53+
}
54+
55+
/**
56+
* An optional section.
57+
*
58+
* When formatting, the section is formatted if the value of any field in the block is not equal to the default value.
59+
* Only [optional] calls where all the fields have default values are permitted.
60+
*
61+
* Example:
62+
* ```
63+
* offsetHours(); char(':'); offsetMinutesOfHour()
64+
* optional { char(':'); offsetSecondsOfMinute() }
65+
* ```
66+
*
67+
* Here, because seconds have the default value of zero, they are formatted only if they are not equal to zero, so the
68+
* UTC offset `+18:30:00` gets formatted as `"+18:30"`, but `+18:30:01` becomes `"+18:30:01"`.
69+
*
70+
* When parsing, either [format] or, if that fails, the literal [ifZero] are parsed. If the [ifZero] string is parsed,
71+
* the values in [format] get assigned their default values.
72+
*
73+
* [ifZero] defines the string that is used if values are the default ones.
74+
*
75+
* @throws IllegalArgumentException if not all fields used in [format] have a default value.
76+
*/
77+
@Suppress("UNCHECKED_CAST")
78+
public fun <T : DateTimeFormatBuilder> T.optional(ifZero: String = "", format: T.() -> Unit): Unit = when (this) {
79+
is AbstractDateTimeFormatBuilder<*, *> -> appendOptionalImpl(
80+
onZero = ifZero,
81+
format as (AbstractDateTimeFormatBuilder<*, *>.() -> Unit)
82+
)
83+
84+
else -> throw IllegalStateException("impossible")
85+
}
86+
87+
/**
88+
* A literal character.
89+
*
90+
* This is a shorthand for `chars(value.toString())`.
91+
*/
92+
public fun DateTimeFormatBuilder.char(value: Char): Unit = chars(value.toString())
93+
94+
internal interface AbstractDateTimeFormatBuilder<Target, ActualSelf> :
95+
DateTimeFormatBuilder where ActualSelf : AbstractDateTimeFormatBuilder<Target, ActualSelf> {
96+
97+
val actualBuilder: AppendableFormatStructure<Target>
98+
fun createEmpty(): ActualSelf
99+
100+
fun appendAlternativeParsingImpl(
101+
vararg otherFormats: ActualSelf.() -> Unit,
102+
mainFormat: ActualSelf.() -> Unit
103+
) {
104+
val others = otherFormats.map { block ->
105+
createEmpty().also { block(it) }.actualBuilder.build()
106+
}
107+
val main = createEmpty().also { mainFormat(it) }.actualBuilder.build()
108+
actualBuilder.add(AlternativesParsingFormatStructure(main, others))
109+
}
110+
111+
fun appendOptionalImpl(
112+
onZero: String,
113+
format: ActualSelf.() -> Unit
114+
) {
115+
actualBuilder.add(OptionalFormatStructure(onZero, createEmpty().also { format(it) }.actualBuilder.build()))
116+
}
117+
118+
override fun chars(value: String) = actualBuilder.add(ConstantFormatStructure(value))
119+
120+
fun build(): CachedFormatStructure<Target> = CachedFormatStructure(actualBuilder.build().formats)
121+
}

0 commit comments

Comments
 (0)