include_tt 1.1.1

Macros for ultra-flexible injection of token trees, literals, or binary data into Rust code from external files during compilation.
Documentation
//! Tracks file dependencies and manages the injection of tracking constants.

use crate::trees::null::make_null_group;
use proc_macro2::{Delimiter, Group, Span, TokenStream as TokenStream2, TokenTree as TokenTree2};
use quote::{format_ident, quote};
use std::path::Path;

/// Tracks file dependencies and manages the injection of tracking constants.
///
/// In the context of `#TRACK_FILES:`, this struct holds references to:
/// 1. `prefix_token`: The `#` symbol.
/// 2. `name_token`: The `TRACK_FILES`/`POINT_TRACKER_FILES` identifier.
/// 3. `data_token`: The `:` punctuation mark, which acts as the insertion point for generated code.
pub(crate) struct FileDepTracker<'tk> {
    /// The `#` symbol.
    prefix_token: &'tk mut TokenTree2,
    /// The `TRACK_FILES`/`POINT_TRACKER_FILES` identifier.
    name_token: &'tk mut TokenTree2,
    /// The `:` punctuation mark, which acts as the insertion point for generated code.
    data_token: &'tk mut TokenTree2,

    c_append_files: usize,
    a_globalposnum: usize,
}

impl<'tk> FileDepTracker<'tk> {
    #[inline]
    pub const fn new(
        a_globalposnum: usize,
        prefix_token: &'tk mut TokenTree2,
        name_token: &'tk mut TokenTree2,
        data_token: &'tk mut TokenTree2,
    ) -> Self {
        Self {
            prefix_token,
            name_token,
            data_token,
            c_append_files: 0,
            a_globalposnum,
        }
    }

    #[inline]
    pub fn prefix_span(&self) -> Span {
        self.prefix_token.span()
    }

    #[inline]
    pub fn name_span(&self) -> Span {
        self.name_token.span()
    }

    #[inline]
    pub fn data_span(&self) -> Span {
        self.data_token.span()
    }

    #[inline]
    pub const fn is_rewritten(&self) -> bool {
        self.c_append_files > 0
    }

    /// Finalizes the tracker and extracts the generated tokens.
    ///
    /// Returns the number of tracked files and the combined `TokenTree`
    /// containing the `include_bytes!` constants.
    pub fn into_token_tree2(self) -> Option<(usize, TokenTree2)> {
        if self.c_append_files == 0 {
            return None;
        }

        let c_append_files = self.c_append_files;
        let data_span = self.data_span();

        let data_token = std::mem::replace(self.data_token, make_null_group(data_span));
        drop(self);

        Some((c_append_files, data_token))
    }

    /// Registers a new file for tracking.
    ///
    /// Generates a unique `const` name and wraps it in a `Delimiter::None` group.
    /// The very first file replaces the `:` token; subsequent files are appended
    /// to the same group.
    pub fn append_track_file(&mut self, path: &Path) {
        let name_const = format_ident!(
            "__TRACKER_FILE_NUM_{}",
            self.a_globalposnum + self.c_append_files
        );

        let abs_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
        let path_str = abs_path.to_string_lossy();
        let ts2 = TokenStream2::from_iter(quote! {
            #[doc(hidden)]
            /// This is a file tracker point, automatically generated by `#TRACK_FILES;`
            const #name_const: &'static [u8] = include_bytes!(#path_str);
        });

        self.append_track_files_ts(ts2)
    }

    /// Injects a raw `TokenStream` into the tracking group.
    ///
    /// If it's the first file being tracked, the original placeholder token (the colon `:`)
    /// is replaced with a `Delimiter::None` group. Subsequent calls append their
    /// streams to this existing group, aggregating all tracking constants in one place.
    pub fn append_track_files_ts(&mut self, ts2: TokenStream2) {
        let data_span = self.data_span();
        let is_initappendfiles = self.c_append_files == 0;
        self.c_append_files += 1;

        let mut ngroup = Group::new(Delimiter::None, ts2);
        ngroup.set_span(data_span);

        if is_initappendfiles {
            *self.data_token = ngroup.into();
        } else {
            match &mut self.data_token {
                TokenTree2::Group(group) => {
                    let mut new_group: Vec<TokenTree2> = group.stream().into_iter().collect();
                    new_group.push(ngroup.into());

                    let mut ngroup =
                        Group::new(Delimiter::None, TokenStream2::from_iter(new_group));
                    ngroup.set_span(data_span);

                    *self.data_token = ngroup.into();
                }
                _ => panic!(
                    "Undefined behavior reported in `PointTrack`, someone redefined `TokenTree2`, expected `TokenTree2::Group`"
                ),
            }
        }
    }
}

impl<'tk> Drop for FileDepTracker<'tk> {
    /// Replaces the control tokens with null groups to hide them from the final output.
    ///
    /// If files were tracked, `data_token` (the colon) becomes the container for constants.
    /// If no files were tracked, `data_token` is also erased.
    fn drop(&mut self) {
        if !self.is_rewritten() {
            let data_span = self.data_span();
            *self.data_token = make_null_group(data_span);
        }
        *self.prefix_token = make_null_group(self.prefix_token.span());
        *self.name_token = make_null_group(self.prefix_token.span());
    }
}