use core::fmt::Write as _;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use crate as genco;
use crate::fmt;
use crate::tokens::ItemStr;
use crate::{quote, quote_in};
pub type Tokens = crate::Tokens<Python>;
impl_lang! {
pub Python {
type Config = Config;
type Format = Format;
type Item = Any;
fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result {
super::c_family_write_quoted(out, input)
}
fn format_file(
tokens: &Tokens,
out: &mut fmt::Formatter<'_>,
config: &Self::Config,
) -> fmt::Result {
let mut imports = Tokens::new();
Self::imports(&mut imports, tokens);
let format = Format::default();
imports.format(out, config, &format)?;
tokens.format(out, config, &format)?;
Ok(())
}
}
Import(Import) {
fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result {
if let TypeModule::Qualified { module, alias } = &self.module {
out.write_str(alias.as_ref().unwrap_or(module))?;
out.write_str(SEP)?;
}
let name = match &self.alias {
Some(alias) => alias,
None => &self.name,
};
out.write_str(name)?;
Ok(())
}
}
ImportModule(ImportModule) {
fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result {
let module = match &self.alias {
Some(alias) => alias,
None => &self.module,
};
out.write_str(module)?;
Ok(())
}
}
}
#[derive(Debug, Default)]
pub struct Format {}
#[derive(Debug, Default)]
pub struct Config {}
static SEP: &str = ".";
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
enum TypeModule {
Unqualified {
module: ItemStr,
},
Qualified {
module: ItemStr,
alias: Option<ItemStr>,
},
}
impl TypeModule {
fn qualified(self) -> Self {
match self {
Self::Unqualified { module } => Self::Qualified {
module,
alias: None,
},
other => other,
}
}
fn with_alias<T>(self, alias: T) -> Self
where
T: Into<ItemStr>,
{
match self {
Self::Qualified { module, .. } | Self::Unqualified { module } => Self::Qualified {
module,
alias: Some(alias.into()),
},
}
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Import {
module: TypeModule,
name: ItemStr,
alias: Option<ItemStr>,
}
impl Import {
pub fn with_alias<T>(self, alias: T) -> Self
where
T: Into<ItemStr>,
{
Self {
alias: Some(alias.into()),
..self
}
}
pub fn qualified(self) -> Self {
Self {
module: self.module.qualified(),
..self
}
}
pub fn with_module_alias<T>(self, module_alias: T) -> Self
where
T: Into<ItemStr>,
{
Self {
module: self.module.with_alias(module_alias),
..self
}
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct ImportModule {
module: ItemStr,
alias: Option<ItemStr>,
}
impl ImportModule {
pub fn with_alias<N>(self, new_alias: N) -> Self
where
N: Into<ItemStr>,
{
Self {
alias: Some(new_alias.into()),
..self
}
}
}
impl Python {
fn imports(out: &mut Tokens, tokens: &Tokens) {
let mut imported_from = BTreeMap::new();
let mut imports = BTreeSet::new();
for import in tokens.iter_lang() {
match import.kind() {
AnyKind::Import(Import {
module,
alias,
name,
}) => match module {
TypeModule::Qualified { module, alias } => {
imports.insert((module, alias));
}
TypeModule::Unqualified { module } => {
imported_from
.entry(module)
.or_insert_with(BTreeSet::new)
.insert((name, alias));
}
},
AnyKind::ImportModule(ImportModule { module, alias }) => {
imports.insert((module, alias));
}
}
}
if imported_from.is_empty() && imports.is_empty() {
return;
}
for (module, imports) in imported_from {
out.push();
let imports = imports
.into_iter()
.map(|(name, alias)| quote!($name$(if let Some(a) = alias => $[' ']as $a)))
.collect::<Vec<_>>();
if imports.len() == 1 {
quote_in! {*out =>
from $module import $(imports.into_iter().next())
}
} else {
quote_in! {*out =>
from $module import $(for i in imports join (, ) => $i)
}
}
}
for (module, alias) in imports {
out.push();
quote_in! {*out =>
import $module$(if let Some(a) = alias => $[' ']as $a)
}
}
out.line();
}
}
pub fn import<M, N>(module: M, name: N) -> Import
where
M: Into<ItemStr>,
N: Into<ItemStr>,
{
Import {
module: TypeModule::Unqualified {
module: module.into(),
},
name: name.into(),
alias: None,
}
}
pub fn import_module<M>(module: M) -> ImportModule
where
M: Into<ItemStr>,
{
ImportModule {
module: module.into(),
alias: None,
}
}