kosame_dsl 0.3.0

Macro-based Rust ORM focused on developer ergonomics
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{
    Ident, Token, parenthesized,
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
};

use crate::{
    command::{Command, CommandType},
    correlations::CorrelationId,
    keyword,
    parse_option::ParseOption,
    part::TableAlias,
    pretty::{BreakMode, Delim, PrettyPrint, Printer},
    visit::Visit,
};

pub struct With {
    pub with_keyword: keyword::with,
    pub items: Punctuated<WithItem, Token![,]>,
}

impl ParseOption for With {
    fn peek(input: ParseStream) -> bool {
        input.peek(keyword::with)
    }
}

pub fn visit_with<'a>(visit: &mut (impl Visit<'a> + ?Sized), with: &'a With) {
    for item in &with.items {
        visit.visit_with_item(item);
    }
}

impl Parse for With {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            with_keyword: input.call(keyword::with::parse_autocomplete)?,
            items: {
                let mut punctuated = Punctuated::new();
                while !input.is_empty() {
                    if CommandType::peek(input) {
                        break;
                    }
                    punctuated.push(input.parse()?);
                    if CommandType::peek(input) {
                        break;
                    }
                    punctuated.push_punct(input.parse()?);
                }
                if punctuated.is_empty() {
                    return Err(syn::Error::new(input.span(), "with clause cannot be empty"));
                }
                punctuated
            },
        })
    }
}

impl ToTokens for With {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let items = self.items.iter();
        quote! { ::kosame::repr::clause::With::new(&[#(#items),*]) }.to_tokens(tokens);
    }
}

impl PrettyPrint for With {
    fn pretty_print(&self, printer: &mut Printer<'_>) {
        self.with_keyword.pretty_print(printer);
        printer.scan_indent(1);
        printer.scan_break();
        " ".pretty_print(printer);
        printer.scan_trivia(false, true);
        self.items.pretty_print(printer);
        printer.scan_indent(-1);
        printer.scan_break();
        " ".pretty_print(printer);
    }
}

pub struct WithItem {
    pub alias: TableAlias,
    pub as_token: Token![as],
    pub paren_token: syn::token::Paren,
    pub command: Command,
    pub correlation_id: CorrelationId,
}

impl WithItem {
    #[must_use]
    pub fn columns(&self) -> Vec<&Ident> {
        match &self.alias.columns {
            Some(columns) => columns.columns.iter().collect(),
            None => self
                .command
                .fields()
                .into_iter()
                .flat_map(|fields| fields.columns())
                .collect(),
        }
    }
}

pub fn visit_with_item<'a>(visit: &mut (impl Visit<'a> + ?Sized), with_item: &'a WithItem) {
    visit.visit_command(&with_item.command);
}

impl Parse for WithItem {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        Ok(Self {
            alias: input.parse()?,
            as_token: input.parse()?,
            paren_token: parenthesized!(content in input),
            command: content.parse()?,
            correlation_id: CorrelationId::new(),
        })
    }
}

impl ToTokens for WithItem {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let alias = &self.alias;
        let command = &self.command;
        quote! { ::kosame::repr::clause::WithItem::new(#alias, #command) }.to_tokens(tokens);
    }
}

impl PrettyPrint for WithItem {
    fn pretty_print(&self, printer: &mut Printer<'_>) {
        self.alias.pretty_print(printer);
        " ".pretty_print(printer);
        self.as_token.pretty_print(printer);
        " ".pretty_print(printer);
        self.paren_token
            .pretty_print(printer, Some(BreakMode::Consistent), |printer| {
                self.command.pretty_print(printer);
            });
    }
}