///|
pub fn render_value_as_textproto(value : Value) -> String {
render_value_as_textproto_with_indent(value, " ")
}
///|
pub fn render_value_as_textproto_with_indent(
value : Value,
indent_text : String,
) -> String {
let buf = StringBuilder::new()
render_textproto_value(value, 0, indent_text, buf)
if !buf.to_string().has_suffix("\n") {
buf.write_char('\n')
}
buf.to_string()
}
///|
pub fn render_value_as_textproto_fragment(
value : Value,
indent_text : String,
) -> String {
let buf = StringBuilder::new()
render_textproto_value(value, 0, indent_text, buf)
textproto_trim_trailing_newline(buf.to_string())
}
///|
pub fn render_value_as_protobuf_fragment(
value : Value,
indent_text : String,
) -> String {
match value {
ThunkValue(_) =>
render_value_as_protobuf_fragment(force_eval_thunk(value), indent_text)
DurationValue(_, _) => {
let buf = StringBuilder::new()
buf.write_string("{\n")
render_textproto_duration_body(value, 1, indent_text, buf)
buf.write_char('}')
buf.to_string()
}
_ => render_value_as_textproto_fragment(value, indent_text)
}
}
///|
fn render_textproto_value(
value : Value,
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
match render_directive_text(value) {
Some(text) => {
buf.write_string(text)
return
}
None => ()
}
match value {
ObjectValue(members) =>
render_textproto_members(members, level, indent_text, buf)
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements)
| SetValue(elements) =>
render_textproto_elements_as_fields(
"it", elements, level, indent_text, buf,
)
MappingValue(entries)
| DefaultedMappingValue(_, entries, _)
| MapValue(entries) =>
render_textproto_entries_as_fields("it", entries, level, indent_text, buf)
DurationValue(_, _) =>
render_textproto_duration_body(value, level, indent_text, buf)
_ => render_textproto_scalar(value, buf)
}
}
///|
fn render_textproto_members(
members : Array[ValueMember],
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
for field in visible_members(members) {
render_textproto_field(
protobuf_member_field_name(field),
field.value,
level,
indent_text,
buf,
)
}
}
///|
fn render_textproto_field(
name : String,
value : Value,
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
match value {
NullValue => ()
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements)
| SetValue(elements) =>
render_textproto_elements_as_fields(
name, elements, level, indent_text, buf,
)
MappingValue(entries)
| DefaultedMappingValue(_, entries, _)
| MapValue(entries) =>
render_textproto_entries_as_fields(name, entries, level, indent_text, buf)
_ => render_textproto_single_field(name, value, level, indent_text, buf)
}
}
///|
fn render_textproto_single_field(
name : String,
value : Value,
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
match render_directive_text(value) {
Some(text) => {
write_textproto_indent(buf, level, indent_text)
buf.write_string(name)
buf.write_string(": ")
buf.write_string(text)
buf.write_char('\n')
return
}
None => ()
}
match value {
ObjectValue(members) => {
let visible = visible_members(members)
write_textproto_indent(buf, level, indent_text)
buf.write_string(name)
if visible.length() == 0 {
buf.write_string(": {}\n")
} else {
buf.write_string(": {\n")
render_textproto_members(members, level + 1, indent_text, buf)
write_textproto_indent(buf, level, indent_text)
buf.write_string("}\n")
}
}
DurationValue(_, _) => {
write_textproto_indent(buf, level, indent_text)
buf.write_string(name)
buf.write_string(": {\n")
render_textproto_duration_body(value, level + 1, indent_text, buf)
write_textproto_indent(buf, level, indent_text)
buf.write_string("}\n")
}
_ => {
write_textproto_indent(buf, level, indent_text)
buf.write_string(name)
buf.write_string(": ")
render_textproto_scalar(value, buf)
buf.write_char('\n')
}
}
}
///|
fn render_textproto_elements_as_fields(
name : String,
elements : Array[Value],
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
for element in elements {
if textproto_needs_collection_wrapper(element) {
render_textproto_wrapped_collection_field(
name, element, level, indent_text, buf,
)
} else {
render_textproto_single_field(name, element, level, indent_text, buf)
}
}
}
///|
fn render_textproto_entries_as_fields(
name : String,
entries : Array[ValueEntry],
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
for entry in entries {
write_textproto_indent(buf, level, indent_text)
buf.write_string(name)
buf.write_string(": {\n")
write_textproto_indent(buf, level + 1, indent_text)
buf.write_string("key: ")
render_textproto_map_key(entry.key, buf)
buf.write_char('\n')
if textproto_needs_collection_wrapper(entry.value) {
render_textproto_wrapped_collection_field(
"value",
entry.value,
level + 1,
indent_text,
buf,
)
} else {
render_textproto_single_field(
"value",
entry.value,
level + 1,
indent_text,
buf,
)
}
write_textproto_indent(buf, level, indent_text)
buf.write_string("}\n")
}
}
///|
fn render_textproto_wrapped_collection_field(
name : String,
value : Value,
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
write_textproto_indent(buf, level, indent_text)
buf.write_string(name)
if textproto_collection_is_empty(value) {
buf.write_string(": {}\n")
return
}
buf.write_string(": {\n")
match value {
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements)
| SetValue(elements) =>
render_textproto_elements_as_fields(
"it",
elements,
level + 1,
indent_text,
buf,
)
MappingValue(entries)
| DefaultedMappingValue(_, entries, _)
| MapValue(entries) =>
render_textproto_entries_as_fields(
"it",
entries,
level + 1,
indent_text,
buf,
)
_ => render_textproto_single_field("it", value, level + 1, indent_text, buf)
}
write_textproto_indent(buf, level, indent_text)
buf.write_string("}\n")
}
///|
fn textproto_needs_collection_wrapper(value : Value) -> Bool {
match value {
ListingValue(_)
| DefaultedListingValue(_, _, _)
| ListValue(_)
| SetValue(_)
| MappingValue(_)
| DefaultedMappingValue(_, _, _)
| MapValue(_) => true
_ => false
}
}
///|
fn textproto_collection_is_empty(value : Value) -> Bool {
match value {
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements)
| SetValue(elements) => elements.length() == 0
MappingValue(entries)
| DefaultedMappingValue(_, entries, _)
| MapValue(entries) => entries.length() == 0
_ => false
}
}
///|
fn render_textproto_duration_body(
value : Value,
level : Int,
indent_text : String,
buf : StringBuilder,
) -> Unit {
match value {
DurationValue(n, unit) => {
let total_ns = duration_in_unit(n, unit, "ns").to_int64()
let seconds = total_ns / 1_000_000_000L
let nanos = total_ns % 1_000_000_000L
write_textproto_indent(buf, level, indent_text)
buf.write_string("seconds: ")
buf.write_string(seconds.to_string())
buf.write_char('\n')
write_textproto_indent(buf, level, indent_text)
buf.write_string("nanos: ")
buf.write_string(nanos.to_string())
buf.write_char('\n')
}
_ => ()
}
}
///|
fn render_textproto_scalar(value : Value, buf : StringBuilder) -> Unit {
match render_directive_text(value) {
Some(text) => {
buf.write_string(text)
return
}
None => ()
}
match value {
ThunkValue(_) => render_textproto_scalar(force_eval_thunk(value), buf)
IntValue(n) => buf.write_string(n.to_string())
FloatValue(d) => buf.write_string(render_float_text(d))
BoolValue(true) => buf.write_string("true")
BoolValue(false) => buf.write_string("false")
StringValue(s) => write_textproto_string(s, buf)
NullValue => buf.write_string("null")
DeferredImportValue(_) => buf.write_string("{}")
RegexValue(pattern) => write_textproto_string(pattern, buf)
BytesValue(bytes) => write_textproto_string(@base64.encode(bytes[:]), buf)
DataSizeValue(n, unit) => write_textproto_string("\{n}.\{unit}", buf)
DurationValue(_, _) => buf.write_string("{}")
PairValue(first, second) => {
buf.write_string("first: ")
render_textproto_scalar(first, buf)
buf.write_string(" second: ")
render_textproto_scalar(second, buf)
}
IntSeqValue(start, end_v, step) => {
let elements = intseq_materialize(start, end_v, step)
buf.write_string("[")
for i = 0; i < elements.length(); i = i + 1 {
if i > 0 {
buf.write_string(", ")
}
render_textproto_scalar(elements[i], buf)
}
buf.write_string("]")
}
ObjectValue(_)
| ListingValue(_)
| DefaultedListingValue(_, _, _)
| ListValue(_)
| MappingValue(_)
| DefaultedMappingValue(_, _, _)
| SetValue(_)
| MapValue(_) => buf.write_string("{}")
FunctionValue(_, _, _, _, _) => buf.write_string("null")
}
}
///|
fn render_textproto_map_key(value : Value, buf : StringBuilder) -> Unit {
match render_directive_text(value) {
Some(text) => {
buf.write_string(text)
return
}
None => ()
}
match value {
StringValue(s) => write_textproto_string(s, buf)
IntValue(n) => buf.write_string(n.to_string())
BoolValue(true) => buf.write_string("true")
BoolValue(false) => buf.write_string("false")
_ => render_textproto_scalar(value, buf)
}
}
///|
fn protobuf_member_field_name(field : ValueMember) -> String {
for annotation in field.annotations {
if annotation.class_name == "Property" ||
annotation.class_name.has_suffix(".Property") {
match protobuf_annotation_string_field(annotation.body_text, "name") {
Some(name) => return name
None => ()
}
}
}
field.name
}
///|
fn protobuf_annotation_string_field(
body : String,
field_name : String,
) -> String? {
let needle = field_name + " = \""
match body.find(needle) {
Some(start) => {
let value_start = start + needle.length()
let rest = String::unsafe_substring(
body,
start=value_start,
end=body.length(),
)
match rest.find("\"") {
Some(end_rel) =>
Some(String::unsafe_substring(rest, start=0, end=end_rel))
None => None
}
}
None => None
}
}
///|
fn write_textproto_indent(
buf : StringBuilder,
level : Int,
indent_text : String,
) -> Unit {
for i = 0; i < level; i = i + 1 {
buf.write_string(indent_text)
}
}
///|
fn write_textproto_string(value : String, buf : StringBuilder) -> Unit {
buf.write_char('"')
for c in value {
match c {
'"' => buf.write_string("\\\"")
'\\' => buf.write_string("\\\\")
'\n' => buf.write_string("\\n")
'\r' => buf.write_string("\\r")
'\t' => buf.write_string("\\t")
'\u{08}' => buf.write_string("\\b")
'\u{0C}' => buf.write_string("\\f")
_ =>
if c < ' ' {
let code = c.to_int()
buf.write_string("\\u")
buf.write_string(json_hex4(code))
} else {
buf.write_char(c)
}
}
}
buf.write_char('"')
}
///|
fn textproto_trim_trailing_newline(value : String) -> String {
if value.has_suffix("\n") {
String::unsafe_substring(value, start=0, end=value.length() - 1)
} else {
value
}
}