mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-30 17:40:00 -07:00
global: snapshot
This commit is contained in:
@@ -63,57 +63,65 @@ class BrkError extends Error {{
|
||||
* @property {{number}} end - End index (exclusive)
|
||||
* @property {{T[]}} data - The metric data
|
||||
*/
|
||||
/** @typedef {{MetricData<unknown>}} AnyMetricData */
|
||||
/** @typedef {{MetricData<any>}} AnyMetricData */
|
||||
|
||||
/**
|
||||
* Thenable interface for await support.
|
||||
* @template T
|
||||
* @typedef {{(onfulfilled?: (value: MetricData<T>) => MetricData<T>, onrejected?: (reason: Error) => never) => Promise<MetricData<T>>}} Thenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metric endpoint builder. Callable (returns itself) so both .by.dateindex and .by.dateindex() work.
|
||||
* @template T
|
||||
* @typedef {{Object}} MetricEndpointBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} first - Fetch first n data points
|
||||
* @property {{(n: number) => RangeBuilder<T>}} last - Fetch last n data points
|
||||
* @property {{(start: number, end: number) => RangeBuilder<T>}} range - Set explicit range [start, end)
|
||||
* @property {{(start: number) => FromBuilder<T>}} from - Set start position, chain with take() or to()
|
||||
* @property {{(end: number) => ToBuilder<T>}} to - Set end position, chain with takeLast() or from()
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON (all data)
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV (all data)
|
||||
* @property {{(index: number) => SingleItemBuilder<T>}} get - Get single item at index
|
||||
* @property {{(start?: number, end?: number) => RangeBuilder<T>}} slice - Slice like Array.slice
|
||||
* @property {{(n: number) => RangeBuilder<T>}} first - Get first n items
|
||||
* @property {{(n: number) => RangeBuilder<T>}} last - Get last n items
|
||||
* @property {{(n: number) => SkippedBuilder<T>}} skip - Skip first n items, chain with take()
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch all data
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
|
||||
* @property {{Thenable<T>}} then - Thenable (await endpoint)
|
||||
* @property {{string}} path - The endpoint path
|
||||
*/
|
||||
/** @typedef {{MetricEndpointBuilder<unknown>}} AnyMetricEndpointBuilder */
|
||||
/** @typedef {{MetricEndpointBuilder<any>}} AnyMetricEndpointBuilder */
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} FromBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items from start position
|
||||
* @property {{(end: number) => RangeBuilder<T>}} to - Set end position
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
* @typedef {{Object}} SingleItemBuilder
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch the item
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{Thenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} ToBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} takeLast - Take last n items before end position
|
||||
* @property {{(start: number) => RangeBuilder<T>}} from - Set start position
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
* @typedef {{Object}} SkippedBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items after skipped position
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch from skipped position to end
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{Thenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} RangeBuilder
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} fetch - Fetch the range
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{Thenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} MetricPattern
|
||||
* @property {{string}} name - The metric name
|
||||
* @property {{Partial<Record<Index, MetricEndpointBuilder<T>>>}} by - Index endpoints (lazy getters)
|
||||
* @property {{Readonly<Partial<Record<Index, MetricEndpointBuilder<T>>>>}} by - Index endpoints as lazy getters. Access via .by.dateindex or .by['dateindex']
|
||||
* @property {{() => Index[]}} indexes - Get the list of available indexes
|
||||
* @property {{(index: Index) => MetricEndpointBuilder<T>|undefined}} get - Get an endpoint for a specific index
|
||||
*/
|
||||
|
||||
/** @typedef {{MetricPattern<unknown>}} AnyMetricPattern */
|
||||
/** @typedef {{MetricPattern<any>}} AnyMetricPattern */
|
||||
|
||||
/**
|
||||
* Create a metric endpoint builder with typestate pattern.
|
||||
@@ -147,50 +155,46 @@ function _endpoint(client, name, index) {{
|
||||
* @returns {{RangeBuilder<T>}}
|
||||
*/
|
||||
const rangeBuilder = (start, end) => ({{
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(start, end), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(start, end, 'csv')); }},
|
||||
fetch(onUpdate) {{ return client.getJson(buildPath(start, end), onUpdate); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(start, end, 'csv')); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
}});
|
||||
|
||||
/**
|
||||
* @param {{number}} index
|
||||
* @returns {{SingleItemBuilder<T>}}
|
||||
*/
|
||||
const singleItemBuilder = (index) => ({{
|
||||
fetch(onUpdate) {{ return client.getJson(buildPath(index, index + 1), onUpdate); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(index, index + 1, 'csv')); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
}});
|
||||
|
||||
/**
|
||||
* @param {{number}} start
|
||||
* @returns {{FromBuilder<T>}}
|
||||
* @returns {{SkippedBuilder<T>}}
|
||||
*/
|
||||
const fromBuilder = (start) => ({{
|
||||
take(/** @type {{number}} */ n) {{ return rangeBuilder(start, start + n); }},
|
||||
to(/** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(start, undefined), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
|
||||
const skippedBuilder = (start) => ({{
|
||||
take(n) {{ return rangeBuilder(start, start + n); }},
|
||||
fetch(onUpdate) {{ return client.getJson(buildPath(start, undefined), onUpdate); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
}});
|
||||
|
||||
/**
|
||||
* @param {{number}} end
|
||||
* @returns {{ToBuilder<T>}}
|
||||
*/
|
||||
const toBuilder = (end) => ({{
|
||||
takeLast(/** @type {{number}} */ n) {{ return rangeBuilder(end - n, end); }},
|
||||
from(/** @type {{number}} */ start) {{ return rangeBuilder(start, end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(undefined, end), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(undefined, end, 'csv')); }},
|
||||
}});
|
||||
|
||||
return {{
|
||||
first(/** @type {{number}} */ n) {{ return rangeBuilder(undefined, n); }},
|
||||
last(/** @type {{number}} */ n) {{ return rangeBuilder(-n, undefined); }},
|
||||
range(/** @type {{number}} */ start, /** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
|
||||
from(/** @type {{number}} */ start) {{ return fromBuilder(start); }},
|
||||
to(/** @type {{number}} */ end) {{ return toBuilder(end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
|
||||
/** @type {{MetricEndpointBuilder<T>}} */
|
||||
const endpoint = {{
|
||||
get(index) {{ return singleItemBuilder(index); }},
|
||||
slice(start, end) {{ return rangeBuilder(start, end); }},
|
||||
first(n) {{ return rangeBuilder(undefined, n); }},
|
||||
last(n) {{ return rangeBuilder(-n, undefined); }},
|
||||
skip(n) {{ return skippedBuilder(n); }},
|
||||
fetch(onUpdate) {{ return client.getJson(buildPath(), onUpdate); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
get path() {{ return p; }},
|
||||
}};
|
||||
|
||||
return endpoint;
|
||||
}}
|
||||
|
||||
/**
|
||||
@@ -235,7 +239,7 @@ class BrkClientBase {{
|
||||
const cachedJson = cachedRes ? await cachedRes.json() : null;
|
||||
|
||||
if (cachedJson) onUpdate?.(cachedJson);
|
||||
if (!globalThis.navigator?.onLine) {{
|
||||
if (globalThis.navigator?.onLine === false) {{
|
||||
if (cachedJson) return cachedJson;
|
||||
throw new BrkError('Offline and no cached data available');
|
||||
}}
|
||||
@@ -305,7 +309,11 @@ pub fn generate_static_constants(output: &mut String) {
|
||||
fn instance_const_camel<T: Serialize>(output: &mut String, name: &str, value: &T) {
|
||||
let json_value: Value = serde_json::to_value(value).unwrap();
|
||||
let camel_value = camel_case_top_level_keys(json_value);
|
||||
write_static_const(output, name, &serde_json::to_string_pretty(&camel_value).unwrap());
|
||||
write_static_const(
|
||||
output,
|
||||
name,
|
||||
&serde_json::to_string_pretty(&camel_value).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
instance_const_camel(output, "TERM_NAMES", &TERM_NAMES);
|
||||
@@ -336,13 +344,25 @@ fn camel_case_top_level_keys(value: Value) -> Value {
|
||||
fn indent_json_const(json: &str) -> String {
|
||||
json.lines()
|
||||
.enumerate()
|
||||
.map(|(i, line)| if i == 0 { line.to_string() } else { format!(" {}", line) })
|
||||
.map(|(i, line)| {
|
||||
if i == 0 {
|
||||
line.to_string()
|
||||
} else {
|
||||
format!(" {}", line)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn write_static_const(output: &mut String, name: &str, json: &str) {
|
||||
writeln!(output, " {} = /** @type {{const}} */ ({});\n", name, indent_json_const(json)).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" {} = /** @type {{const}} */ ({});\n",
|
||||
name,
|
||||
indent_json_const(json)
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Generate index accessor factory functions.
|
||||
@@ -354,14 +374,30 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
writeln!(output, "// Index accessor factory functions\n").unwrap();
|
||||
|
||||
for pattern in patterns {
|
||||
// Use 'readonly' to indicate these are getters (lazy evaluation)
|
||||
let by_fields: Vec<String> = pattern
|
||||
.indexes
|
||||
.iter()
|
||||
.map(|idx| format!("{}: MetricEndpointBuilder<T>", idx.serialize_long()))
|
||||
.map(|idx| {
|
||||
format!(
|
||||
"readonly {}: MetricEndpointBuilder<T>",
|
||||
idx.serialize_long()
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let by_type = format!("{{ {} }}", by_fields.join(", "));
|
||||
|
||||
writeln!(output, "/**").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" * Metric pattern with index endpoints as lazy getters."
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" * Access via property (.by.dateindex) or bracket notation (.by['dateindex'])."
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " * @template T").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
@@ -385,7 +421,11 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
|
||||
for (i, index) in pattern.indexes.iter().enumerate() {
|
||||
let index_name = index.serialize_long();
|
||||
let comma = if i < pattern.indexes.len() - 1 { "," } else { "" };
|
||||
let comma = if i < pattern.indexes.len() - 1 {
|
||||
","
|
||||
} else {
|
||||
""
|
||||
};
|
||||
writeln!(
|
||||
output,
|
||||
" get {}() {{ return _endpoint(client, name, '{}'); }}{}",
|
||||
@@ -438,8 +478,12 @@ pub fn generate_structural_patterns(
|
||||
}
|
||||
writeln!(output, " * @typedef {{Object}} {}", pattern.name).unwrap();
|
||||
for field in &pattern.fields {
|
||||
let js_type =
|
||||
metadata.field_type_annotation(field, pattern.is_generic, None, GenericSyntax::JAVASCRIPT);
|
||||
let js_type = metadata.field_type_annotation(
|
||||
field,
|
||||
pattern.is_generic,
|
||||
None,
|
||||
GenericSyntax::JAVASCRIPT,
|
||||
);
|
||||
writeln!(
|
||||
output,
|
||||
" * @property {{{}}} {}",
|
||||
@@ -469,8 +513,17 @@ pub fn generate_structural_patterns(
|
||||
writeln!(output, " * @returns {{{}}}", return_type).unwrap();
|
||||
writeln!(output, " */").unwrap();
|
||||
|
||||
let param_name = if is_parameterizable { "acc" } else { "basePath" };
|
||||
writeln!(output, "function create{}(client, {}) {{", pattern.name, param_name).unwrap();
|
||||
let param_name = if is_parameterizable {
|
||||
"acc"
|
||||
} else {
|
||||
"basePath"
|
||||
};
|
||||
writeln!(
|
||||
output,
|
||||
"function create{}(client, {}) {{",
|
||||
pattern.name, param_name
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " return {{").unwrap();
|
||||
|
||||
let syntax = JavaScriptSyntax;
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::fmt::Write;
|
||||
use brk_types::TreeNode;
|
||||
|
||||
use crate::{
|
||||
ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField, child_type_name,
|
||||
ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField,
|
||||
generate_leaf_field, get_first_leaf_name, get_node_fields, get_pattern_instance_base,
|
||||
infer_accumulated_name, prepare_tree_node, to_camel_case,
|
||||
};
|
||||
@@ -45,43 +45,41 @@ fn generate_tree_typedef(
|
||||
writeln!(output, "/**").unwrap();
|
||||
writeln!(output, " * @typedef {{Object}} {}", name).unwrap();
|
||||
|
||||
for ((field, child_fields), (child_name, _)) in
|
||||
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
|
||||
{
|
||||
let js_type = metadata.resolve_tree_field_type(
|
||||
field,
|
||||
child_fields.as_deref(),
|
||||
name,
|
||||
child_name,
|
||||
GenericSyntax::JAVASCRIPT,
|
||||
);
|
||||
for child in &ctx.children {
|
||||
let js_type = if child.should_inline {
|
||||
child.inline_type_name.clone()
|
||||
} else {
|
||||
metadata.resolve_tree_field_type(
|
||||
&child.field,
|
||||
child.child_fields.as_deref(),
|
||||
name,
|
||||
child.name,
|
||||
GenericSyntax::JAVASCRIPT,
|
||||
)
|
||||
};
|
||||
|
||||
writeln!(
|
||||
output,
|
||||
" * @property {{{}}} {}",
|
||||
js_type,
|
||||
to_camel_case(&field.name)
|
||||
to_camel_case(&child.field.name)
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, " */\n").unwrap();
|
||||
|
||||
for (child_name, child_node) in ctx.children {
|
||||
if let TreeNode::Branch(grandchildren) = child_node {
|
||||
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
||||
// Generate typedef if no pattern match OR pattern is not parameterizable
|
||||
if !metadata.is_parameterizable_fields(&child_fields) {
|
||||
let child_type = child_type_name(name, child_name);
|
||||
generate_tree_typedef(
|
||||
output,
|
||||
&child_type,
|
||||
child_node,
|
||||
pattern_lookup,
|
||||
metadata,
|
||||
generated,
|
||||
);
|
||||
}
|
||||
// Generate child typedefs
|
||||
for child in &ctx.children {
|
||||
if child.should_inline {
|
||||
generate_tree_typedef(
|
||||
output,
|
||||
&child.inline_type_name,
|
||||
child.node,
|
||||
pattern_lookup,
|
||||
metadata,
|
||||
generated,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,12 +180,14 @@ fn generate_tree_initializer(
|
||||
.get(&child_fields)
|
||||
.filter(|name| metadata.is_parameterizable(name));
|
||||
|
||||
if let Some(pattern_name) = pattern_name {
|
||||
let arg = get_pattern_instance_base(child_node);
|
||||
let base_result = get_pattern_instance_base(child_node);
|
||||
|
||||
// Use pattern factory only if no outlier was detected
|
||||
if let Some(pattern_name) = pattern_name.filter(|_| !base_result.has_outlier) {
|
||||
writeln!(
|
||||
output,
|
||||
"{}{}: create{}(this, '{}'),",
|
||||
indent_str, field_name, pattern_name, arg
|
||||
indent_str, field_name, pattern_name, base_result.base
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
|
||||
@@ -195,143 +195,138 @@ class _EndpointConfig:
|
||||
|
||||
|
||||
class RangeBuilder(Generic[T]):
|
||||
"""Final builder with range fully specified. Can only call json() or csv()."""
|
||||
"""Builder with range specified."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data."""
|
||||
def fetch(self) -> MetricData[T]:
|
||||
"""Fetch the range as parsed JSON."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
def fetch_csv(self) -> str:
|
||||
"""Fetch the range as CSV string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class FromBuilder(Generic[T]):
|
||||
"""Builder after calling from(start). Can chain with take() or to()."""
|
||||
class SingleItemBuilder(Generic[T]):
|
||||
"""Builder for single item access."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def fetch(self) -> MetricData[T]:
|
||||
"""Fetch the single item."""
|
||||
return self._config.get_json()
|
||||
|
||||
def fetch_csv(self) -> str:
|
||||
"""Fetch as CSV."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class SkippedBuilder(Generic[T]):
|
||||
"""Builder after calling skip(n). Chain with take() to specify count."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def take(self, n: int) -> RangeBuilder[T]:
|
||||
"""Take n items from the start position."""
|
||||
"""Take n items after the skipped position."""
|
||||
start = self._config.start or 0
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, start + n
|
||||
))
|
||||
|
||||
def to(self, end: int) -> RangeBuilder[T]:
|
||||
"""Set the end position."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
self._config.start, end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (from start to end of data)."""
|
||||
def fetch(self) -> MetricData[T]:
|
||||
"""Fetch from skipped position to end."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class ToBuilder(Generic[T]):
|
||||
"""Builder after calling to(end). Can chain with take_last() or from()."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def take_last(self, n: int) -> RangeBuilder[T]:
|
||||
"""Take last n items before the end position."""
|
||||
end = self._config.end or 0
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
end - n, end
|
||||
))
|
||||
|
||||
def from_(self, start: int) -> RangeBuilder[T]:
|
||||
"""Set the start position."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, self._config.end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (from start of data to end)."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
def fetch_csv(self) -> str:
|
||||
"""Fetch as CSV."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class MetricEndpointBuilder(Generic[T]):
|
||||
"""Initial builder for metric endpoint queries.
|
||||
"""Builder for metric endpoint queries.
|
||||
|
||||
Use method chaining to specify the data range, then call json() or csv() to execute.
|
||||
Use method chaining to specify the data range, then call fetch() or fetch_csv() to execute.
|
||||
|
||||
Examples:
|
||||
# Get all data
|
||||
endpoint.json()
|
||||
# Fetch all data
|
||||
data = endpoint.fetch()
|
||||
|
||||
# Get last 10 points
|
||||
endpoint.last(10).json()
|
||||
# Single item access
|
||||
data = endpoint[5].fetch()
|
||||
|
||||
# Get range [100, 200)
|
||||
endpoint.range(100, 200).json()
|
||||
# Slice syntax (Python-native)
|
||||
data = endpoint[:10].fetch() # First 10
|
||||
data = endpoint[-5:].fetch() # Last 5
|
||||
data = endpoint[100:110].fetch() # Range
|
||||
|
||||
# Get 10 points starting from position 100
|
||||
endpoint.from_(100).take(10).json()
|
||||
# Convenience methods (pandas-style)
|
||||
data = endpoint.head().fetch() # First 10 (default)
|
||||
data = endpoint.head(20).fetch() # First 20
|
||||
data = endpoint.tail(5).fetch() # Last 5
|
||||
|
||||
# Iterator-style chaining
|
||||
data = endpoint.skip(100).take(10).fetch()
|
||||
"""
|
||||
|
||||
def __init__(self, client: BrkClientBase, name: str, index: Index):
|
||||
self._config = _EndpointConfig(client, name, index)
|
||||
|
||||
def first(self, n: int) -> RangeBuilder[T]:
|
||||
"""Fetch the first n data points."""
|
||||
@overload
|
||||
def __getitem__(self, key: int) -> SingleItemBuilder[T]: ...
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> RangeBuilder[T]: ...
|
||||
|
||||
def __getitem__(self, key: Union[int, slice]) -> Union[SingleItemBuilder[T], RangeBuilder[T]]:
|
||||
"""Access single item or slice.
|
||||
|
||||
Examples:
|
||||
endpoint[5] # Single item at index 5
|
||||
endpoint[:10] # First 10
|
||||
endpoint[-5:] # Last 5
|
||||
endpoint[100:110] # Range 100-109
|
||||
"""
|
||||
if isinstance(key, int):
|
||||
return SingleItemBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
key, key + 1
|
||||
))
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
key.start, key.stop
|
||||
))
|
||||
|
||||
def head(self, n: int = 10) -> RangeBuilder[T]:
|
||||
"""Get the first n items (pandas-style)."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
None, n
|
||||
))
|
||||
|
||||
def last(self, n: int) -> RangeBuilder[T]:
|
||||
"""Fetch the last n data points."""
|
||||
def tail(self, n: int = 10) -> RangeBuilder[T]:
|
||||
"""Get the last n items (pandas-style)."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
-n, None
|
||||
))
|
||||
|
||||
def range(self, start: int, end: int) -> RangeBuilder[T]:
|
||||
"""Set an explicit range [start, end)."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
def skip(self, n: int) -> SkippedBuilder[T]:
|
||||
"""Skip the first n items. Chain with take() to get a range."""
|
||||
return SkippedBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, end
|
||||
n, None
|
||||
))
|
||||
|
||||
def from_(self, start: int) -> FromBuilder[T]:
|
||||
"""Set the start position. Chain with take() or to()."""
|
||||
return FromBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, None
|
||||
))
|
||||
|
||||
def to(self, end: int) -> ToBuilder[T]:
|
||||
"""Set the end position. Chain with take_last() or from_()."""
|
||||
return ToBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
None, end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (all data)."""
|
||||
def fetch(self) -> MetricData[T]:
|
||||
"""Fetch all data as parsed JSON."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string (all data)."""
|
||||
def fetch_csv(self) -> str:
|
||||
"""Fetch all data as CSV string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
def path(self) -> str:
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn generate_python_client(
|
||||
writeln!(output, "# Do not edit manually\n").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol"
|
||||
"from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol, overload"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, "from http.client import HTTPSConnection, HTTPConnection").unwrap();
|
||||
|
||||
@@ -6,8 +6,8 @@ use std::fmt::Write;
|
||||
use brk_types::TreeNode;
|
||||
|
||||
use crate::{
|
||||
ClientMetadata, GenericSyntax, PatternField, PythonSyntax, child_type_name, generate_leaf_field,
|
||||
get_node_fields, get_pattern_instance_base, prepare_tree_node, to_snake_case,
|
||||
ClientMetadata, GenericSyntax, PatternField, PythonSyntax, generate_leaf_field,
|
||||
prepare_tree_node, to_snake_case,
|
||||
};
|
||||
|
||||
/// Generate tree classes
|
||||
@@ -41,22 +41,16 @@ fn generate_tree_class(
|
||||
|
||||
// Generate child classes FIRST (post-order traversal)
|
||||
// This ensures children are defined before parent references them
|
||||
for (child_name, child_node) in ctx.children.iter() {
|
||||
if let TreeNode::Branch(grandchildren) = child_node {
|
||||
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
||||
|
||||
// Generate inline class if no pattern match OR pattern is not parameterizable
|
||||
if !metadata.is_parameterizable_fields(&child_fields) {
|
||||
let child_class = child_type_name(name, child_name);
|
||||
generate_tree_class(
|
||||
output,
|
||||
&child_class,
|
||||
child_node,
|
||||
pattern_lookup,
|
||||
metadata,
|
||||
generated,
|
||||
);
|
||||
}
|
||||
for child in &ctx.children {
|
||||
if child.should_inline {
|
||||
generate_tree_class(
|
||||
output,
|
||||
&child.inline_type_name,
|
||||
child.node,
|
||||
pattern_lookup,
|
||||
metadata,
|
||||
generated,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,45 +65,36 @@ fn generate_tree_class(
|
||||
.unwrap();
|
||||
|
||||
let syntax = PythonSyntax;
|
||||
for ((field, child_fields_opt), (child_name, child_node)) in
|
||||
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
|
||||
{
|
||||
let py_type = metadata.resolve_tree_field_type(
|
||||
field,
|
||||
child_fields_opt.as_deref(),
|
||||
name,
|
||||
child_name,
|
||||
GenericSyntax::PYTHON,
|
||||
);
|
||||
let field_name_py = to_snake_case(&field.name);
|
||||
for child in &ctx.children {
|
||||
let field_name_py = to_snake_case(child.name);
|
||||
|
||||
if metadata.is_pattern_type(&field.rust_type) && metadata.is_parameterizable(&field.rust_type)
|
||||
{
|
||||
// Parameterizable pattern: use pattern class with metric base
|
||||
let metric_base = get_pattern_instance_base(child_node);
|
||||
writeln!(
|
||||
output,
|
||||
" self.{}: {} = {}(client, '{}')",
|
||||
field_name_py, py_type, field.rust_type, metric_base
|
||||
)
|
||||
.unwrap();
|
||||
} else if let TreeNode::Leaf(leaf) = child_node {
|
||||
// Leaf node: use shared helper
|
||||
generate_leaf_field(output, &syntax, "client", child_name, leaf, metadata, " ");
|
||||
} else if field.is_branch() {
|
||||
// Non-parameterizable pattern or regular branch: generate inline class
|
||||
let inline_class = child_type_name(name, &field.name);
|
||||
if child.is_leaf {
|
||||
if let TreeNode::Leaf(leaf) = child.node {
|
||||
generate_leaf_field(output, &syntax, "client", child.name, leaf, metadata, " ");
|
||||
}
|
||||
} else if child.should_inline {
|
||||
// Inline class
|
||||
writeln!(
|
||||
output,
|
||||
" self.{}: {} = {}(client)",
|
||||
field_name_py, inline_class, inline_class
|
||||
field_name_py, child.inline_type_name, child.inline_type_name
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!(
|
||||
"Field '{}' has no matching index pattern. All metrics must be indexed.",
|
||||
field.name
|
||||
// Use pattern class with metric base
|
||||
let py_type = metadata.resolve_tree_field_type(
|
||||
&child.field,
|
||||
child.child_fields.as_deref(),
|
||||
name,
|
||||
child.name,
|
||||
GenericSyntax::PYTHON,
|
||||
);
|
||||
writeln!(
|
||||
output,
|
||||
" self.{}: {} = {}(client, '{}')",
|
||||
field_name_py, py_type, child.field.rust_type, child.base_result.base
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ pub fn generate_imports(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
r#"use std::sync::Arc;
|
||||
use std::ops::{{Bound, RangeBounds}};
|
||||
use serde::de::DeserializeOwned;
|
||||
pub use brk_cohort::*;
|
||||
pub use brk_types::*;
|
||||
@@ -193,21 +194,30 @@ impl EndpointConfig {{
|
||||
|
||||
/// Initial builder for metric endpoint queries.
|
||||
///
|
||||
/// Use method chaining to specify the data range, then call `json()` or `csv()` to execute.
|
||||
/// Use method chaining to specify the data range, then call `fetch()` or `fetch_csv()` to execute.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// // Get all data
|
||||
/// endpoint.json()?;
|
||||
/// // Fetch all data
|
||||
/// let data = endpoint.fetch()?;
|
||||
///
|
||||
/// // Get last 10 points
|
||||
/// endpoint.last(10).json()?;
|
||||
/// // Get single item at index 5
|
||||
/// let data = endpoint.get(5).fetch()?;
|
||||
///
|
||||
/// // Get first 10 using range
|
||||
/// let data = endpoint.range(..10).fetch()?;
|
||||
///
|
||||
/// // Get range [100, 200)
|
||||
/// endpoint.range(100, 200).json()?;
|
||||
/// let data = endpoint.range(100..200).fetch()?;
|
||||
///
|
||||
/// // Get 10 points starting from position 100
|
||||
/// endpoint.from(100).take(10).json()?;
|
||||
/// // Get first 10 (convenience)
|
||||
/// let data = endpoint.take(10).fetch()?;
|
||||
///
|
||||
/// // Get last 10
|
||||
/// let data = endpoint.last(10).fetch()?;
|
||||
///
|
||||
/// // Iterator-style chaining
|
||||
/// let data = endpoint.skip(100).take(10).fetch()?;
|
||||
/// ```
|
||||
pub struct MetricEndpointBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
@@ -219,44 +229,59 @@ impl<T: DeserializeOwned> MetricEndpointBuilder<T> {{
|
||||
Self {{ config: EndpointConfig::new(client, name, index), _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Fetch the first n data points.
|
||||
pub fn first(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
self.config.end = Some(n as i64);
|
||||
/// Select a specific index position.
|
||||
pub fn get(mut self, index: usize) -> SingleItemBuilder<T> {{
|
||||
self.config.start = Some(index as i64);
|
||||
self.config.end = Some(index as i64 + 1);
|
||||
SingleItemBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Select a range using Rust range syntax.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// endpoint.range(..10) // first 10
|
||||
/// endpoint.range(100..110) // indices 100-109
|
||||
/// endpoint.range(100..) // from 100 to end
|
||||
/// ```
|
||||
pub fn range<R: RangeBounds<usize>>(mut self, range: R) -> RangeBuilder<T> {{
|
||||
self.config.start = match range.start_bound() {{
|
||||
Bound::Included(&n) => Some(n as i64),
|
||||
Bound::Excluded(&n) => Some(n as i64 + 1),
|
||||
Bound::Unbounded => None,
|
||||
}};
|
||||
self.config.end = match range.end_bound() {{
|
||||
Bound::Included(&n) => Some(n as i64 + 1),
|
||||
Bound::Excluded(&n) => Some(n as i64),
|
||||
Bound::Unbounded => None,
|
||||
}};
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Fetch the last n data points.
|
||||
pub fn last(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
/// Take the first n items.
|
||||
pub fn take(self, n: usize) -> RangeBuilder<T> {{
|
||||
self.range(..n)
|
||||
}}
|
||||
|
||||
/// Take the last n items.
|
||||
pub fn last(mut self, n: usize) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(-(n as i64));
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set an explicit range [start, end).
|
||||
pub fn range(mut self, start: i64, end: i64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
self.config.end = Some(end);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
/// Skip the first n items. Chain with `take(n)` to get a range.
|
||||
pub fn skip(mut self, n: usize) -> SkippedBuilder<T> {{
|
||||
self.config.start = Some(n as i64);
|
||||
SkippedBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the start position. Chain with `take(n)` or `to(end)`.
|
||||
pub fn from(mut self, start: i64) -> FromBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
FromBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the end position. Chain with `takeLast(n)` or `from(start)`.
|
||||
pub fn to(mut self, end: i64) -> ToBuilder<T> {{
|
||||
self.config.end = Some(end);
|
||||
ToBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (all data).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
/// Fetch all data as parsed JSON.
|
||||
pub fn fetch(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string (all data).
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
/// Fetch all data as CSV string.
|
||||
pub fn fetch_csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
|
||||
@@ -266,82 +291,63 @@ impl<T: DeserializeOwned> MetricEndpointBuilder<T> {{
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Builder after calling `from(start)`. Can chain with `take(n)` or `to(end)`.
|
||||
pub struct FromBuilder<T> {{
|
||||
/// Builder for single item access.
|
||||
pub struct SingleItemBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> FromBuilder<T> {{
|
||||
/// Take n items from the start position.
|
||||
pub fn take(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
impl<T: DeserializeOwned> SingleItemBuilder<T> {{
|
||||
/// Fetch the single item.
|
||||
pub fn fetch(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Fetch the single item as CSV.
|
||||
pub fn fetch_csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Builder after calling `skip(n)`. Chain with `take(n)` to specify count.
|
||||
pub struct SkippedBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> SkippedBuilder<T> {{
|
||||
/// Take n items after the skipped position.
|
||||
pub fn take(mut self, n: usize) -> RangeBuilder<T> {{
|
||||
let start = self.config.start.unwrap_or(0);
|
||||
self.config.end = Some(start + n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the end position.
|
||||
pub fn to(mut self, end: i64) -> RangeBuilder<T> {{
|
||||
self.config.end = Some(end);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (from start to end of data).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
/// Fetch from the skipped position to the end.
|
||||
pub fn fetch(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
/// Fetch from the skipped position to the end as CSV.
|
||||
pub fn fetch_csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Builder after calling `to(end)`. Can chain with `takeLast(n)` or `from(start)`.
|
||||
pub struct ToBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> ToBuilder<T> {{
|
||||
/// Take last n items before the end position.
|
||||
pub fn take_last(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
let end = self.config.end.unwrap_or(0);
|
||||
self.config.start = Some(end - n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the start position.
|
||||
pub fn from(mut self, start: i64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (from start of data to end).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Final builder with range fully specified. Can only call `json()` or `csv()`.
|
||||
/// Builder with range fully specified.
|
||||
pub struct RangeBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> RangeBuilder<T> {{
|
||||
/// Execute the query and return parsed JSON data.
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
/// Fetch the range as parsed JSON.
|
||||
pub fn fetch(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
/// Fetch the range as CSV string.
|
||||
pub fn fetch_csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
@@ -6,9 +6,8 @@ use std::fmt::Write;
|
||||
use brk_types::TreeNode;
|
||||
|
||||
use crate::{
|
||||
ClientMetadata, GenericSyntax, LanguageSyntax, PatternField, RustSyntax, child_type_name,
|
||||
generate_leaf_field, generate_tree_node_field, get_node_fields, get_pattern_instance_base,
|
||||
prepare_tree_node, to_snake_case,
|
||||
ClientMetadata, GenericSyntax, LanguageSyntax, PatternField, RustSyntax,
|
||||
generate_leaf_field, generate_tree_node_field, prepare_tree_node, to_snake_case,
|
||||
};
|
||||
|
||||
/// Generate tree structs.
|
||||
@@ -39,25 +38,29 @@ fn generate_tree_node(
|
||||
return;
|
||||
};
|
||||
|
||||
// Generate struct definition
|
||||
writeln!(output, "/// Metrics tree node.").unwrap();
|
||||
writeln!(output, "pub struct {} {{", name).unwrap();
|
||||
|
||||
for ((field, child_fields), (child_name, _)) in
|
||||
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
|
||||
{
|
||||
let field_name = to_snake_case(&field.name);
|
||||
let type_annotation = metadata.resolve_tree_field_type(
|
||||
field,
|
||||
child_fields.as_deref(),
|
||||
name,
|
||||
child_name,
|
||||
GenericSyntax::RUST,
|
||||
);
|
||||
for child in &ctx.children {
|
||||
let field_name = to_snake_case(child.name);
|
||||
let type_annotation = if child.should_inline {
|
||||
child.inline_type_name.clone()
|
||||
} else {
|
||||
metadata.resolve_tree_field_type(
|
||||
&child.field,
|
||||
child.child_fields.as_deref(),
|
||||
name,
|
||||
child.name,
|
||||
GenericSyntax::RUST,
|
||||
)
|
||||
};
|
||||
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
// Generate impl block
|
||||
writeln!(output, "impl {} {{", name).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
@@ -67,53 +70,40 @@ fn generate_tree_node(
|
||||
writeln!(output, " Self {{").unwrap();
|
||||
|
||||
let syntax = RustSyntax;
|
||||
for ((field_info, child_fields), (child_name, child_node)) in
|
||||
ctx.fields_with_child_info.iter().zip(ctx.children.iter())
|
||||
{
|
||||
let field_name = to_snake_case(&field_info.name);
|
||||
for child in &ctx.children {
|
||||
let field_name = to_snake_case(child.name);
|
||||
|
||||
// Check if this is a pattern type and if it's parameterizable
|
||||
let is_parameterizable = child_fields
|
||||
.as_ref()
|
||||
.is_some_and(|cf| metadata.is_parameterizable_fields(cf));
|
||||
|
||||
if metadata.is_pattern_type(&field_info.rust_type) && is_parameterizable {
|
||||
// Parameterizable pattern: use pattern constructor with metric base
|
||||
let pattern_base = get_pattern_instance_base(child_node);
|
||||
generate_tree_node_field(
|
||||
output,
|
||||
&syntax,
|
||||
field_info,
|
||||
metadata,
|
||||
" ",
|
||||
child_name,
|
||||
Some(&pattern_base),
|
||||
);
|
||||
} else if child_fields.is_some() {
|
||||
// Non-parameterizable pattern or regular branch: use inline struct
|
||||
let child_struct = child_type_name(name, child_name);
|
||||
let path_expr = syntax.path_expr("base_path", &format!("_{}", child_name));
|
||||
if child.is_leaf {
|
||||
if let TreeNode::Leaf(leaf) = child.node {
|
||||
generate_leaf_field(
|
||||
output,
|
||||
&syntax,
|
||||
"client.clone()",
|
||||
child.name,
|
||||
leaf,
|
||||
metadata,
|
||||
" ",
|
||||
);
|
||||
}
|
||||
} else if child.should_inline {
|
||||
// Inline struct
|
||||
let path_expr = syntax.path_expr("base_path", &format!("_{}", child.name));
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client.clone(), {}),",
|
||||
field_name, child_struct, path_expr
|
||||
field_name, child.inline_type_name, path_expr
|
||||
)
|
||||
.unwrap();
|
||||
} else if let TreeNode::Leaf(leaf) = child_node {
|
||||
// Leaf field - use shared helper
|
||||
generate_leaf_field(
|
||||
} else {
|
||||
// Use pattern constructor
|
||||
generate_tree_node_field(
|
||||
output,
|
||||
&syntax,
|
||||
"client.clone()",
|
||||
child_name,
|
||||
leaf,
|
||||
&child.field,
|
||||
metadata,
|
||||
" ",
|
||||
);
|
||||
} else {
|
||||
panic!(
|
||||
"Field '{}' is a leaf with no TreeNode::Leaf. This shouldn't happen.",
|
||||
field_info.name
|
||||
child.name,
|
||||
Some(&child.base_result.base),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -122,21 +112,17 @@ fn generate_tree_node(
|
||||
writeln!(output, " }}").unwrap();
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
for (child_name, child_node) in ctx.children {
|
||||
if let TreeNode::Branch(grandchildren) = child_node {
|
||||
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
||||
// Generate child struct if no pattern match OR pattern is not parameterizable
|
||||
if !metadata.is_parameterizable_fields(&child_fields) {
|
||||
let child_struct = child_type_name(name, child_name);
|
||||
generate_tree_node(
|
||||
output,
|
||||
&child_struct,
|
||||
child_node,
|
||||
pattern_lookup,
|
||||
metadata,
|
||||
generated,
|
||||
);
|
||||
}
|
||||
// Generate child structs
|
||||
for child in &ctx.children {
|
||||
if child.should_inline {
|
||||
generate_tree_node(
|
||||
output,
|
||||
&child.inline_type_name,
|
||||
child.node,
|
||||
pattern_lookup,
|
||||
metadata,
|
||||
generated,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user