libgir/codegen/
general.rs

1use std::{
2    collections::BTreeMap,
3    fmt::{Display, Write as FmtWrite},
4    io::{Result, Write},
5    ops::Index,
6};
7
8use super::Visibility;
9use crate::{
10    analysis::{
11        self,
12        general::StatusedTypeId,
13        imports::{ImportConditions, Imports},
14        namespaces,
15        special_functions::TraitInfo,
16    },
17    config::{derives::Derive, Config},
18    env::Env,
19    gir_version::VERSION,
20    library::TypeId,
21    nameutil::use_glib_type,
22    version::Version,
23    writer::primitives::tabs,
24};
25
26pub fn start_comments(w: &mut dyn Write, conf: &Config) -> Result<()> {
27    if conf.single_version_file.is_some() {
28        start_comments_no_version(w, conf)
29    } else {
30        single_version_file(w, conf, "// ")?;
31        writeln!(w, "// DO NOT EDIT")
32    }
33}
34
35pub fn start_comments_no_version(w: &mut dyn Write, conf: &Config) -> Result<()> {
36    writeln!(
37        w,
38        "// This file was generated by gir (https://github.com/gtk-rs/gir)"
39    )?;
40    write!(
41        w,
42        "{}",
43        conf.girs_version
44            .iter()
45            .fold(String::new(), |mut output, info| {
46                writeln!(
47                    output,
48                    "// from {}{}",
49                    info.gir_dir.display(),
50                    info.get_repository_url()
51                        .map_or_else(String::new, |url| format!(" ({url})")),
52                )
53                .unwrap();
54                output
55            })
56    )?;
57    writeln!(w, "// DO NOT EDIT")?;
58    Ok(())
59}
60
61pub fn single_version_file(w: &mut dyn Write, conf: &Config, prefix: &str) -> Result<()> {
62    write!(
63        w,
64        "{}Generated by gir (https://github.com/gtk-rs/gir @ {})
65{}",
66        prefix,
67        VERSION,
68        conf.girs_version
69            .iter()
70            .fold(String::new(), |mut output, info| {
71                match (info.get_repository_url(), info.get_hash()) {
72                    (Some(url), Some(hash)) => writeln!(
73                        output,
74                        "{}from {} ({} @ {})",
75                        prefix,
76                        info.gir_dir.display(),
77                        url,
78                        hash,
79                    ),
80                    (None, Some(hash)) => {
81                        writeln!(
82                            output,
83                            "{}from {} (@ {})",
84                            prefix,
85                            info.gir_dir.display(),
86                            hash,
87                        )
88                    }
89                    _ => writeln!(output, "{}from {}", prefix, info.gir_dir.display()),
90                }
91                .unwrap();
92                output
93            }),
94    )
95}
96
97pub fn uses(
98    w: &mut dyn Write,
99    env: &Env,
100    imports: &Imports,
101    outer_version: Option<Version>,
102) -> Result<()> {
103    writeln!(w)?;
104
105    let mut grouped_imports: BTreeMap<(&str, Option<ImportConditions>), Vec<&str>> =
106        BTreeMap::new();
107
108    for (name, scope) in imports.iter() {
109        let (crate_name, import) = name.split_once("::").unwrap();
110        let mut scope = scope.clone();
111
112        // The check here is needed to group unneeded version guards and allow grouping those imports
113        scope.version = Version::if_stricter_than(scope.version, outer_version);
114        let to_compare_with = env.config.min_required_version(env, None);
115        scope.version = match (scope.version, to_compare_with) {
116            (Some(v), Some(to_compare_v)) => {
117                if v > to_compare_v {
118                    scope.version
119                } else {
120                    None
121                }
122            }
123            (Some(_), _) => scope.version,
124            _ => None,
125        };
126
127        let key = if scope.constraints.is_empty() && scope.version.is_none() {
128            (crate_name, None)
129        } else {
130            (crate_name, Some(scope))
131        };
132        grouped_imports
133            .entry(key)
134            .and_modify(|entry| entry.push(import))
135            .or_insert_with(|| vec![import]);
136    }
137
138    for ((crate_name, scope), names) in grouped_imports.iter() {
139        if !scope.is_none() {
140            let scope = scope.as_ref().unwrap();
141            if !scope.constraints.is_empty() {
142                if scope.constraints.len() == 1 {
143                    writeln!(w, "#[cfg({})]", scope.constraints[0])?;
144                } else {
145                    writeln!(w, "#[cfg(any({}))]", scope.constraints.join(", "))?;
146                }
147                writeln!(
148                    w,
149                    "#[cfg_attr(docsrs, doc(cfg({})))]",
150                    scope.constraints.join(", ")
151                )?;
152            }
153            version_condition(w, env, None, scope.version, false, 0)?;
154        }
155        writeln!(w, "use {crate_name}::{{{}}};", names.join(","))?;
156    }
157
158    Ok(())
159}
160
161fn format_parent_name(env: &Env, p: &StatusedTypeId) -> String {
162    if p.type_id.ns_id == namespaces::MAIN {
163        p.name.clone()
164    } else {
165        format!(
166            "{krate}::{name}",
167            krate = env.namespaces[p.type_id.ns_id].crate_name,
168            name = p.name,
169        )
170    }
171}
172
173pub fn define_fundamental_type(
174    w: &mut dyn Write,
175    env: &Env,
176    type_name: &str,
177    glib_name: &str,
178    glib_func_name: &str,
179    ref_func: Option<&str>,
180    unref_func: Option<&str>,
181    parents: &[StatusedTypeId],
182    visibility: Visibility,
183    type_id: TypeId,
184) -> Result<()> {
185    let sys_crate_name = env.sys_crate_import(type_id);
186    writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?;
187    doc_alias(w, glib_name, "", 1)?;
188    external_doc_link(
189        w,
190        env.config.external_docs_url.as_deref(),
191        type_name,
192        &visibility,
193        1,
194    )?;
195    writeln!(
196        w,
197        "\t{visibility} struct {type_name}(Shared<{sys_crate_name}::{glib_name}>);"
198    )?;
199    writeln!(w)?;
200
201    writeln!(w, "\tmatch fn {{")?;
202    let (ref_fn, unref_fn, ptr, ffi_crate_name) = if parents.is_empty() {
203        // it's the super-type, it must have a ref/unref functions
204        (
205            ref_func.unwrap().to_owned(),
206            unref_func.unwrap().to_owned(),
207            "ptr".to_owned(),
208            sys_crate_name.to_owned(),
209        )
210    } else {
211        let (ref_fn, unref_fn, ptr, ffi_crate_name) = parents
212            .iter()
213            .find_map(|p| {
214                use crate::library::*;
215                let type_ = env.library.type_(p.type_id);
216                let parent_sys_crate_name = env.sys_crate_import(p.type_id);
217                match type_ {
218                    Type::Class(class) => Some((
219                        class.ref_fn.as_ref().unwrap().clone(),
220                        class.unref_fn.as_ref().unwrap().clone(),
221                        format!(
222                            "ptr as *mut {}::{}",
223                            parent_sys_crate_name,
224                            class.c_type.clone()
225                        ),
226                        parent_sys_crate_name,
227                    )),
228                    _ => None,
229                }
230            })
231            .unwrap();
232        // otherwise get that information from the parent
233        (ref_fn, unref_fn, ptr, ffi_crate_name)
234    };
235
236    writeln!(w, "\t\tref => |ptr| {ffi_crate_name}::{ref_fn}({ptr}),")?;
237    writeln!(w, "\t\tunref => |ptr| {ffi_crate_name}::{unref_fn}({ptr}),")?;
238
239    writeln!(w, "\t}}")?;
240    writeln!(w, "}}")?;
241
242    // We can't use type_ from glib::wrapper! because that would auto-implement
243    // Value traits which are often not the correct types
244    writeln!(w, "\n")?;
245    writeln!(w, "impl StaticType for {} {{", type_name)?;
246    writeln!(w, "\tfn static_type() -> {} {{", use_glib_type(env, "Type"))?;
247    writeln!(
248        w,
249        "\t\t unsafe {{ from_glib({sys_crate_name}::{glib_func_name}()) }}"
250    )?;
251    writeln!(w, "\t}}")?;
252    writeln!(w, "}}")?;
253    Ok(())
254}
255
256pub fn define_object_type(
257    w: &mut dyn Write,
258    env: &Env,
259    type_name: &str,
260    glib_name: &str,
261    glib_class_name: Option<&str>,
262    glib_func_name: &str,
263    is_interface: bool,
264    parents: &[StatusedTypeId],
265    visibility: Visibility,
266    type_id: TypeId,
267) -> Result<()> {
268    let sys_crate_name = env.sys_crate_import(type_id);
269    let class_name = {
270        if let Some(s) = glib_class_name {
271            format!(", {sys_crate_name}::{s}")
272        } else {
273            String::new()
274        }
275    };
276
277    let kind_name = if is_interface { "Interface" } else { "Object" };
278
279    let parents: Vec<StatusedTypeId> = parents
280        .iter()
281        .filter(|p| !p.status.ignored())
282        .cloned()
283        .collect();
284
285    writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?;
286    doc_alias(w, glib_name, "", 1)?;
287    external_doc_link(
288        w,
289        env.config.external_docs_url.as_deref(),
290        type_name,
291        &visibility,
292        1,
293    )?;
294    if parents.is_empty() {
295        writeln!(
296            w,
297            "\t{visibility} struct {type_name}({kind_name}<{sys_crate_name}::{glib_name}{class_name}>);"
298        )?;
299    } else if is_interface {
300        let prerequisites: Vec<String> =
301            parents.iter().map(|p| format_parent_name(env, p)).collect();
302
303        writeln!(
304            w,
305            "\t{} struct {}(Interface<{}::{}{}>) @requires {};",
306            visibility,
307            type_name,
308            sys_crate_name,
309            glib_name,
310            class_name,
311            prerequisites.join(", ")
312        )?;
313    } else {
314        let interfaces: Vec<String> = parents
315            .iter()
316            .filter(|p| {
317                use crate::library::*;
318
319                matches!(
320                    *env.library.type_(p.type_id),
321                    Type::Interface { .. } if !p.status.ignored()
322                )
323            })
324            .map(|p| format_parent_name(env, p))
325            .collect();
326
327        let parents: Vec<String> = parents
328            .iter()
329            .filter(|p| {
330                use crate::library::*;
331
332                matches!(
333                    *env.library.type_(p.type_id),
334                    Type::Class { .. } if !p.status.ignored()
335                )
336            })
337            .map(|p| format_parent_name(env, p))
338            .collect();
339
340        let mut parents_string = String::new();
341        if !parents.is_empty() {
342            parents_string.push_str(format!(" @extends {}", parents.join(", ")).as_str());
343        }
344
345        if !interfaces.is_empty() {
346            if !parents.is_empty() {
347                parents_string.push(',');
348            }
349            parents_string.push_str(format!(" @implements {}", interfaces.join(", ")).as_str());
350        }
351
352        writeln!(
353            w,
354            "\t{visibility} struct {type_name}(Object<{sys_crate_name}::{glib_name}{class_name}>){parents_string};",
355        )?;
356    }
357    writeln!(w)?;
358    writeln!(w, "\tmatch fn {{")?;
359    writeln!(w, "\t\ttype_ => || {sys_crate_name}::{glib_func_name}(),")?;
360    writeln!(w, "\t}}")?;
361    writeln!(w, "}}")?;
362
363    Ok(())
364}
365
366fn define_boxed_type_internal(
367    w: &mut dyn Write,
368    env: &Env,
369    type_name: &str,
370    glib_name: &str,
371    copy_fn: &TraitInfo,
372    free_fn: &str,
373    boxed_inline: bool,
374    init_function_expression: &Option<String>,
375    copy_into_function_expression: &Option<String>,
376    clear_function_expression: &Option<String>,
377    get_type_fn: Option<&str>,
378    derive: &[Derive],
379    visibility: Visibility,
380    type_id: TypeId,
381) -> Result<()> {
382    let sys_crate_name = env.sys_crate_import(type_id);
383    writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?;
384
385    derives(w, derive, 1)?;
386    writeln!(
387        w,
388        "\t{} struct {}(Boxed{}<{}::{}>);",
389        visibility,
390        type_name,
391        if boxed_inline { "Inline" } else { "" },
392        sys_crate_name,
393        glib_name
394    )?;
395    writeln!(w)?;
396    writeln!(w, "\tmatch fn {{")?;
397    let mut_ov = if copy_fn.first_parameter_mut {
398        "mut_override(ptr)"
399    } else {
400        "ptr"
401    };
402    writeln!(
403        w,
404        "\t\tcopy => |ptr| {}::{}({}),",
405        sys_crate_name, copy_fn.glib_name, mut_ov
406    )?;
407    writeln!(w, "\t\tfree => |ptr| {sys_crate_name}::{free_fn}(ptr),")?;
408
409    if let (
410        Some(init_function_expression),
411        Some(copy_into_function_expression),
412        Some(clear_function_expression),
413    ) = (
414        init_function_expression,
415        copy_into_function_expression,
416        clear_function_expression,
417    ) {
418        writeln!(w, "\t\tinit => {init_function_expression},",)?;
419        writeln!(w, "\t\tcopy_into => {copy_into_function_expression},",)?;
420        writeln!(w, "\t\tclear => {clear_function_expression},",)?;
421    }
422
423    if let Some(get_type_fn) = get_type_fn {
424        writeln!(w, "\t\ttype_ => || {sys_crate_name}::{get_type_fn}(),")?;
425    }
426    writeln!(w, "\t}}")?;
427    writeln!(w, "}}")?;
428
429    Ok(())
430}
431
432pub fn define_boxed_type(
433    w: &mut dyn Write,
434    env: &Env,
435    type_name: &str,
436    glib_name: &str,
437    copy_fn: &TraitInfo,
438    free_fn: &str,
439    boxed_inline: bool,
440    init_function_expression: &Option<String>,
441    copy_into_function_expression: &Option<String>,
442    clear_function_expression: &Option<String>,
443    get_type_fn: Option<(String, Option<Version>)>,
444    derive: &[Derive],
445    visibility: Visibility,
446    type_id: TypeId,
447) -> Result<()> {
448    writeln!(w)?;
449
450    if let Some((ref get_type_fn, get_type_version)) = get_type_fn {
451        if get_type_version.is_some() {
452            version_condition(w, env, None, get_type_version, false, 0)?;
453            define_boxed_type_internal(
454                w,
455                env,
456                type_name,
457                glib_name,
458                copy_fn,
459                free_fn,
460                boxed_inline,
461                init_function_expression,
462                copy_into_function_expression,
463                clear_function_expression,
464                Some(get_type_fn),
465                derive,
466                visibility,
467                type_id,
468            )?;
469
470            writeln!(w)?;
471            not_version_condition_no_docsrs(w, env, None, get_type_version, false, 0)?;
472            define_boxed_type_internal(
473                w,
474                env,
475                type_name,
476                glib_name,
477                copy_fn,
478                free_fn,
479                boxed_inline,
480                init_function_expression,
481                copy_into_function_expression,
482                clear_function_expression,
483                None,
484                derive,
485                visibility,
486                type_id,
487            )?;
488        } else {
489            define_boxed_type_internal(
490                w,
491                env,
492                type_name,
493                glib_name,
494                copy_fn,
495                free_fn,
496                boxed_inline,
497                init_function_expression,
498                copy_into_function_expression,
499                clear_function_expression,
500                Some(get_type_fn),
501                derive,
502                visibility,
503                type_id,
504            )?;
505        }
506    } else {
507        define_boxed_type_internal(
508            w,
509            env,
510            type_name,
511            glib_name,
512            copy_fn,
513            free_fn,
514            boxed_inline,
515            init_function_expression,
516            copy_into_function_expression,
517            clear_function_expression,
518            None,
519            derive,
520            visibility,
521            type_id,
522        )?;
523    }
524
525    Ok(())
526}
527
528pub fn define_auto_boxed_type(
529    w: &mut dyn Write,
530    env: &Env,
531    type_name: &str,
532    glib_name: &str,
533    boxed_inline: bool,
534    init_function_expression: &Option<String>,
535    copy_into_function_expression: &Option<String>,
536    clear_function_expression: &Option<String>,
537    get_type_fn: &str,
538    derive: &[Derive],
539    visibility: Visibility,
540    type_id: TypeId,
541) -> Result<()> {
542    let sys_crate_name = env.sys_crate_import(type_id);
543    writeln!(w)?;
544    writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?;
545    derives(w, derive, 1)?;
546    writeln!(
547        w,
548        "\t{} struct {}(Boxed{}<{}::{}>);",
549        visibility,
550        type_name,
551        if boxed_inline { "Inline" } else { "" },
552        sys_crate_name,
553        glib_name
554    )?;
555    writeln!(w)?;
556    writeln!(w, "\tmatch fn {{")?;
557    writeln!(
558        w,
559        "\t\tcopy => |ptr| {}({}::{}(), ptr as *mut _) as *mut {}::{},",
560        use_glib_type(env, "gobject_ffi::g_boxed_copy"),
561        sys_crate_name,
562        get_type_fn,
563        sys_crate_name,
564        glib_name
565    )?;
566    writeln!(
567        w,
568        "\t\tfree => |ptr| {}({}::{}(), ptr as *mut _),",
569        use_glib_type(env, "gobject_ffi::g_boxed_free"),
570        sys_crate_name,
571        get_type_fn
572    )?;
573
574    if let (
575        Some(init_function_expression),
576        Some(copy_into_function_expression),
577        Some(clear_function_expression),
578    ) = (
579        init_function_expression,
580        copy_into_function_expression,
581        clear_function_expression,
582    ) {
583        writeln!(w, "\t\tinit => {init_function_expression},",)?;
584        writeln!(w, "\t\tcopy_into => {copy_into_function_expression},",)?;
585        writeln!(w, "\t\tclear => {clear_function_expression},",)?;
586    }
587
588    writeln!(w, "\t\ttype_ => || {sys_crate_name}::{get_type_fn}(),")?;
589    writeln!(w, "\t}}")?;
590    writeln!(w, "}}")?;
591
592    Ok(())
593}
594
595fn define_shared_type_internal(
596    w: &mut dyn Write,
597    env: &Env,
598    type_name: &str,
599    glib_name: &str,
600    ref_fn: &str,
601    unref_fn: &str,
602    get_type_fn: Option<&str>,
603    derive: &[Derive],
604    visibility: Visibility,
605    type_id: TypeId,
606) -> Result<()> {
607    let sys_crate_name = env.sys_crate_import(type_id);
608    writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?;
609    derives(w, derive, 1)?;
610    writeln!(
611        w,
612        "\t{visibility} struct {type_name}(Shared<{sys_crate_name}::{glib_name}>);"
613    )?;
614    writeln!(w)?;
615    writeln!(w, "\tmatch fn {{")?;
616    writeln!(w, "\t\tref => |ptr| {sys_crate_name}::{ref_fn}(ptr),")?;
617    writeln!(w, "\t\tunref => |ptr| {sys_crate_name}::{unref_fn}(ptr),")?;
618    if let Some(get_type_fn) = get_type_fn {
619        writeln!(w, "\t\ttype_ => || {sys_crate_name}::{get_type_fn}(),")?;
620    }
621    writeln!(w, "\t}}")?;
622    writeln!(w, "}}")?;
623
624    Ok(())
625}
626
627pub fn define_shared_type(
628    w: &mut dyn Write,
629    env: &Env,
630    type_name: &str,
631    glib_name: &str,
632    ref_fn: &str,
633    unref_fn: &str,
634    get_type_fn: Option<(String, Option<Version>)>,
635    derive: &[Derive],
636    visibility: Visibility,
637    type_id: TypeId,
638) -> Result<()> {
639    writeln!(w)?;
640
641    if let Some((ref get_type_fn, get_type_version)) = get_type_fn {
642        if get_type_version.is_some() {
643            version_condition(w, env, None, get_type_version, false, 0)?;
644            define_shared_type_internal(
645                w,
646                env,
647                type_name,
648                glib_name,
649                ref_fn,
650                unref_fn,
651                Some(get_type_fn),
652                derive,
653                visibility,
654                type_id,
655            )?;
656
657            writeln!(w)?;
658            not_version_condition_no_docsrs(w, env, None, get_type_version, false, 0)?;
659            define_shared_type_internal(
660                w, env, type_name, glib_name, ref_fn, unref_fn, None, derive, visibility, type_id,
661            )?;
662        } else {
663            define_shared_type_internal(
664                w,
665                env,
666                type_name,
667                glib_name,
668                ref_fn,
669                unref_fn,
670                Some(get_type_fn),
671                derive,
672                visibility,
673                type_id,
674            )?;
675        }
676    } else {
677        define_shared_type_internal(
678            w, env, type_name, glib_name, ref_fn, unref_fn, None, derive, visibility, type_id,
679        )?;
680    }
681
682    Ok(())
683}
684
685pub fn cfg_deprecated(
686    w: &mut dyn Write,
687    env: &Env,
688    type_tid: Option<TypeId>,
689    deprecated: Option<Version>,
690    commented: bool,
691    indent: usize,
692) -> Result<()> {
693    if let Some(s) = cfg_deprecated_string(env, type_tid, deprecated, commented, indent) {
694        writeln!(w, "{s}")?;
695    }
696    Ok(())
697}
698
699pub fn cfg_deprecated_string(
700    env: &Env,
701    type_tid: Option<TypeId>,
702    deprecated: Option<Version>,
703    commented: bool,
704    indent: usize,
705) -> Option<String> {
706    let comment = if commented { "//" } else { "" };
707    deprecated.map(|v| {
708        if env.is_too_low_version(type_tid.map(|t| t.ns_id), Some(v)) {
709            format!("{}{}#[deprecated = \"Since {}\"]", tabs(indent), comment, v)
710        } else {
711            format!(
712                "{}{}#[cfg_attr({}, deprecated = \"Since {}\")]",
713                tabs(indent),
714                comment,
715                v.to_cfg(None),
716                v,
717            )
718        }
719    })
720}
721
722pub fn version_condition(
723    w: &mut dyn Write,
724    env: &Env,
725    ns_id: Option<u16>,
726    version: Option<Version>,
727    commented: bool,
728    indent: usize,
729) -> Result<()> {
730    if let Some(s) = version_condition_string(env, ns_id, version, commented, indent) {
731        writeln!(w, "{s}")?;
732    }
733    Ok(())
734}
735
736pub fn version_condition_no_doc(
737    w: &mut dyn Write,
738    env: &Env,
739    ns_id: Option<u16>,
740    version: Option<Version>,
741    commented: bool,
742    indent: usize,
743) -> Result<()> {
744    let to_compare_with = env.config.min_required_version(env, ns_id);
745    let should_generate = match (version, to_compare_with) {
746        (Some(v), Some(to_compare_v)) => v > to_compare_v,
747        (Some(_), _) => true,
748        _ => false,
749    };
750    if should_generate {
751        // Prefix with the crate name if it's not the main one
752        let namespace_name = ns_id.and_then(|ns| {
753            if ns == namespaces::MAIN {
754                None
755            } else {
756                Some(env.namespaces.index(ns).crate_name.clone())
757            }
758        });
759        if let Some(s) = cfg_condition_string_no_doc(
760            Some(&version.unwrap().to_cfg(namespace_name.as_deref())),
761            commented,
762            indent,
763        ) {
764            writeln!(w, "{s}")?;
765        }
766    }
767    Ok(())
768}
769pub fn version_condition_doc(
770    w: &mut dyn Write,
771    env: &Env,
772    version: Option<Version>,
773    commented: bool,
774    indent: usize,
775) -> Result<()> {
776    match version {
777        Some(v) if v > env.config.min_cfg_version => {
778            if let Some(s) = cfg_condition_string_doc(Some(&v.to_cfg(None)), commented, indent) {
779                writeln!(w, "{s}")?;
780            }
781        }
782        _ => {}
783    }
784    Ok(())
785}
786
787pub fn version_condition_string(
788    env: &Env,
789    ns_id: Option<u16>,
790    version: Option<Version>,
791    commented: bool,
792    indent: usize,
793) -> Option<String> {
794    let to_compare_with = env.config.min_required_version(env, ns_id);
795    let should_generate = match (version, to_compare_with) {
796        (Some(v), Some(to_compare_v)) => v > to_compare_v,
797        (Some(_), _) => true,
798        _ => false,
799    };
800    if should_generate {
801        // Prefix with the crate name if it's not the main one
802        let namespace_name = ns_id.and_then(|ns| {
803            if ns == namespaces::MAIN {
804                None
805            } else {
806                Some(env.namespaces.index(ns).crate_name.clone())
807            }
808        });
809        cfg_condition_string(
810            Some(&version.unwrap().to_cfg(namespace_name.as_deref())),
811            commented,
812            indent,
813        )
814    } else {
815        None
816    }
817}
818
819pub fn not_version_condition(
820    w: &mut dyn Write,
821    version: Option<Version>,
822    commented: bool,
823    indent: usize,
824) -> Result<()> {
825    if let Some(s) = version.and_then(|v| {
826        cfg_condition_string(Some(&format!("not({})", v.to_cfg(None))), commented, indent)
827    }) {
828        writeln!(w, "{s}")?;
829    }
830    Ok(())
831}
832
833pub fn not_version_condition_no_docsrs(
834    w: &mut dyn Write,
835    env: &Env,
836    ns_id: Option<u16>,
837    version: Option<Version>,
838    commented: bool,
839    indent: usize,
840) -> Result<()> {
841    if let Some(v) = version {
842        let comment = if commented { "//" } else { "" };
843        let namespace_name = ns_id.and_then(|ns| {
844            if ns == namespaces::MAIN {
845                None
846            } else {
847                Some(env.namespaces.index(ns).crate_name.clone())
848            }
849        });
850        let s = format!(
851            "{}{}#[cfg(not(any({})))]",
852            tabs(indent),
853            comment,
854            v.to_cfg(namespace_name.as_deref())
855        );
856        writeln!(w, "{s}")?;
857    }
858    Ok(())
859}
860
861pub fn cfg_condition(
862    w: &mut dyn Write,
863    cfg_condition: Option<&(impl Display + ?Sized)>,
864    commented: bool,
865    indent: usize,
866) -> Result<()> {
867    if let Some(s) = cfg_condition_string(cfg_condition, commented, indent) {
868        writeln!(w, "{s}")?;
869    }
870    Ok(())
871}
872
873pub fn cfg_condition_no_doc(
874    w: &mut dyn Write,
875    cfg_condition: Option<&(impl Display + ?Sized)>,
876    commented: bool,
877    indent: usize,
878) -> Result<()> {
879    if let Some(s) = cfg_condition_string_no_doc(cfg_condition, commented, indent) {
880        writeln!(w, "{s}")?;
881    }
882    Ok(())
883}
884
885pub fn cfg_condition_string_no_doc(
886    cfg_condition: Option<&(impl Display + ?Sized)>,
887    commented: bool,
888    indent: usize,
889) -> Option<String> {
890    cfg_condition.map(|cfg| {
891        let comment = if commented { "//" } else { "" };
892        format!("{0}{1}#[cfg({2})]", tabs(indent), comment, cfg)
893    })
894}
895
896pub fn cfg_condition_doc(
897    w: &mut dyn Write,
898    cfg_condition: Option<&(impl Display + ?Sized)>,
899    commented: bool,
900    indent: usize,
901) -> Result<()> {
902    if let Some(s) = cfg_condition_string_doc(cfg_condition, commented, indent) {
903        writeln!(w, "{s}")?;
904    }
905    Ok(())
906}
907
908pub fn cfg_condition_string_doc(
909    cfg_condition: Option<&(impl Display + ?Sized)>,
910    commented: bool,
911    indent: usize,
912) -> Option<String> {
913    cfg_condition.map(|cfg| {
914        let comment = if commented { "//" } else { "" };
915        format!(
916            "{0}{1}#[cfg_attr(docsrs, doc(cfg({2})))]",
917            tabs(indent),
918            comment,
919            cfg,
920        )
921    })
922}
923
924pub fn cfg_condition_string(
925    cfg_condition: Option<&(impl Display + ?Sized)>,
926    commented: bool,
927    indent: usize,
928) -> Option<String> {
929    cfg_condition.map(|_| {
930        format!(
931            "{}\n{}",
932            cfg_condition_string_no_doc(cfg_condition, commented, indent).unwrap(),
933            cfg_condition_string_doc(cfg_condition, commented, indent).unwrap(),
934        )
935    })
936}
937
938pub fn derives(w: &mut dyn Write, derives: &[Derive], indent: usize) -> Result<()> {
939    for derive in derives {
940        let s = match &derive.cfg_condition {
941            Some(condition) => format!(
942                "#[cfg_attr({}, derive({}))]",
943                condition,
944                derive.names.join(", ")
945            ),
946            None => format!("#[derive({})]", derive.names.join(", ")),
947        };
948        writeln!(w, "{}{}", tabs(indent), s)?;
949    }
950    Ok(())
951}
952
953pub fn doc_alias(w: &mut dyn Write, name: &str, comment_prefix: &str, indent: usize) -> Result<()> {
954    writeln!(
955        w,
956        "{}{}#[doc(alias = \"{}\")]",
957        tabs(indent),
958        comment_prefix,
959        name,
960    )
961}
962
963pub fn external_doc_link(
964    w: &mut dyn Write,
965    external_url: Option<&str>,
966    name: &str,
967    visibility: &Visibility,
968    indent: usize,
969) -> Result<()> {
970    // Don't generate the external doc link on non-public types.
971    if !visibility.is_public() {
972        Ok(())
973    } else if let Some(external_url) = external_url {
974        writeln!(
975            w,
976            "{}/// This documentation is incomplete due to license restrictions and limitations on docs.rs. Please have a look at [our official docs]({}/index.html?search={}) for more information.",
977            tabs(indent),
978            external_url.trim_end_matches('/'),
979            name
980        )
981    } else {
982        Ok(())
983    }
984}
985
986pub fn doc_hidden(
987    w: &mut dyn Write,
988    doc_hidden: bool,
989    comment_prefix: &str,
990    indent: usize,
991) -> Result<()> {
992    if doc_hidden {
993        writeln!(w, "{}{}#[doc(hidden)]", tabs(indent), comment_prefix)
994    } else {
995        Ok(())
996    }
997}
998
999pub fn allow_deprecated(
1000    w: &mut dyn Write,
1001    allow_deprecated: Option<Version>,
1002    commented: bool,
1003    indent: usize,
1004) -> Result<()> {
1005    if allow_deprecated.is_some() {
1006        writeln!(
1007            w,
1008            "{}{}#[allow(deprecated)]",
1009            tabs(indent),
1010            if commented { "//" } else { "" }
1011        )
1012    } else {
1013        Ok(())
1014    }
1015}
1016
1017pub fn write_vec<T: Display>(w: &mut dyn Write, v: &[T]) -> Result<()> {
1018    for s in v {
1019        writeln!(w, "{s}")?;
1020    }
1021    Ok(())
1022}
1023
1024pub fn declare_default_from_new(
1025    w: &mut dyn Write,
1026    env: &Env,
1027    name: &str,
1028    functions: &[analysis::functions::Info],
1029    has_builder: bool,
1030) -> Result<()> {
1031    if let Some(func) = functions.iter().find(|f| {
1032        !f.hidden
1033            && f.status.need_generate()
1034            && f.name == "new"
1035            // Cannot generate Default implementation for Option<>
1036            && f.ret.parameter.as_ref().is_some_and(|x| !*x.lib_par.nullable)
1037    }) {
1038        if func.parameters.rust_parameters.is_empty() {
1039            writeln!(w)?;
1040            version_condition(w, env, None, func.version, false, 0)?;
1041            writeln!(
1042                w,
1043                "impl Default for {name} {{
1044                     fn default() -> Self {{
1045                         Self::new()
1046                     }}
1047                 }}"
1048            )?;
1049        } else if has_builder {
1050            // create an alternative default implementation the uses `glib::object::Object::new()`
1051            writeln!(w)?;
1052            version_condition(w, env, None, func.version, false, 0)?;
1053            writeln!(
1054                w,
1055                "impl Default for {name} {{
1056                     fn default() -> Self {{
1057                         glib::object::Object::new::<Self>()
1058                     }}
1059                 }}"
1060            )?;
1061        }
1062    }
1063
1064    Ok(())
1065}
1066
1067/// Escapes string in format suitable for placing inside double quotes.
1068pub fn escape_string(s: &str) -> String {
1069    let mut es = String::with_capacity(s.len() * 2);
1070    for c in s.chars() {
1071        match c {
1072            '\"' | '\\' => es.push('\\'),
1073            _ => (),
1074        }
1075        es.push(c);
1076    }
1077    es
1078}
1079
1080#[cfg(test)]
1081mod tests {
1082    use super::*;
1083
1084    #[test]
1085    fn test_escape_string() {
1086        assert_eq!(escape_string(""), "");
1087        assert_eq!(escape_string("no escaping here"), "no escaping here");
1088        assert_eq!(escape_string(r#"'"\"#), r#"'\"\\"#);
1089    }
1090}