halo_space/
builder.rs

1//! Build / BuildNamed / Buildf: helpers for format-style SQL builders.
2
3use crate::args::Args;
4use crate::flavor::Flavor;
5use crate::modifiers::{Arg, Builder, escape, named};
6
7#[derive(Debug, Clone)]
8struct CompiledBuilder {
9    args: Args,
10    format: String,
11}
12
13impl CompiledBuilder {
14    fn new(args: Args, format: String) -> Self {
15        Self { args, format }
16    }
17}
18
19impl Builder for CompiledBuilder {
20    fn build_with_flavor(&self, flavor: Flavor, initial_arg: &[Arg]) -> (String, Vec<Arg>) {
21        self.args
22            .compile_with_flavor(&self.format, flavor, initial_arg)
23    }
24
25    fn flavor(&self) -> Flavor {
26        self.args.flavor
27    }
28}
29
30#[derive(Clone)]
31struct FlavoredBuilder {
32    inner: Box<dyn Builder>,
33    flavor: Flavor,
34}
35
36impl Builder for FlavoredBuilder {
37    fn build_with_flavor(&self, flavor: Flavor, initial_arg: &[Arg]) -> (String, Vec<Arg>) {
38        self.inner.build_with_flavor(flavor, initial_arg)
39    }
40
41    fn flavor(&self) -> Flavor {
42        self.flavor
43    }
44}
45
46/// WithFlavor: bind a default flavor to a builder.
47pub fn with_flavor(builder: impl Builder + 'static, flavor: Flavor) -> Box<dyn Builder> {
48    Box::new(FlavoredBuilder {
49        inner: Box::new(builder),
50        flavor,
51    })
52}
53
54/// Build: construct a builder using `$` placeholders.
55pub fn build(
56    format: impl Into<String>,
57    args_in: impl IntoIterator<Item = impl Into<Arg>>,
58) -> Box<dyn Builder> {
59    let mut args = Args::default();
60    for a in args_in {
61        args.add(a);
62    }
63    Box::new(CompiledBuilder::new(args, format.into()))
64}
65
66/// BuildNamed: enable only `${name}` and `$$`, sourcing args from a map.
67pub fn build_named(
68    format: impl Into<String>,
69    named_map: impl IntoIterator<Item = (String, Arg)>,
70) -> Box<dyn Builder> {
71    let mut args = Args {
72        only_named: true,
73        ..Args::default()
74    };
75
76    for (k, v) in named_map {
77        args.add(named(k, v));
78    }
79
80    Box::new(CompiledBuilder::new(args, format.into()))
81}
82
83/// Buildf: fmt-like builder supporting `%v`/`%s` only.
84pub fn buildf(format: &str, args_in: impl IntoIterator<Item = impl Into<Arg>>) -> Box<dyn Builder> {
85    let mut args = Args::default();
86    let escaped = escape(format);
87    let mut out = String::new();
88
89    let mut it = args_in.into_iter();
90    let mut chars = escaped.chars().peekable();
91    while let Some(c) = chars.next() {
92        if c == '%' {
93            match chars.peek().copied() {
94                Some('v') | Some('s') => {
95                    chars.next();
96                    if let Some(a) = it.next() {
97                        let ph = args.add(a.into());
98                        out.push_str(&ph);
99                    } else {
100                        // 没有足够参数:按字面输出,保持行为可见
101                        out.push('%');
102                        out.push('v');
103                    }
104                }
105                Some('%') => {
106                    chars.next();
107                    out.push('%');
108                }
109                _ => out.push('%'),
110            }
111        } else {
112            out.push(c);
113        }
114    }
115
116    // Ignore extra arguments, mirroring fmt-like behavior (unused args are dropped).
117    Box::new(CompiledBuilder::new(args, out))
118}