use crate::{Cons, Formatter, Lang, LangItem, Quoted};
use std::collections::BTreeSet;
use std::fmt::{self, Write};
pub type Tokens<'el> = crate::Tokens<'el, Go>;
impl_type_basics!(Go, TypeEnum<'a>, TypeTrait, TypeBox, TypeArgs, {Type, Map, Array, Interface});
pub trait TypeTrait: 'static + fmt::Debug + LangItem<Go> {
fn as_enum(&self) -> TypeEnum<'_>;
fn type_imports(&self, _: &mut BTreeSet<Cons<'static>>) {}
}
pub const INTERFACE: Interface = Interface(());
const SEP: &str = ".";
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Type {
module: Option<Cons<'static>>,
name: Cons<'static>,
}
impl TypeTrait for Type {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Type(self)
}
fn type_imports(&self, modules: &mut BTreeSet<Cons<'static>>) {
if let Some(module) = &self.module {
modules.insert(module.clone());
}
}
}
impl LangItem<Go> for Type {
fn format(&self, out: &mut Formatter, _: &mut Config, _: usize) -> fmt::Result {
if let Some(module) = self.module.as_ref().and_then(|m| m.split("/").last()) {
out.write_str(module)?;
out.write_str(SEP)?;
}
out.write_str(&self.name)?;
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Map {
key: TypeBox,
value: TypeBox,
}
impl TypeTrait for Map {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Map(self)
}
fn type_imports(&self, modules: &mut BTreeSet<Cons<'static>>) {
self.key.type_imports(modules);
self.value.type_imports(modules);
}
}
impl LangItem<Go> for Map {
fn format(&self, out: &mut Formatter, config: &mut Config, level: usize) -> fmt::Result {
out.write_str("map[")?;
self.key.format(out, config, level + 1)?;
out.write_str("]")?;
self.value.format(out, config, level + 1)?;
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Array {
inner: TypeBox,
}
impl TypeTrait for Array {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Array(self)
}
fn type_imports(&self, modules: &mut BTreeSet<Cons<'static>>) {
self.inner.type_imports(modules);
}
}
impl LangItem<Go> for Array {
fn format(&self, out: &mut Formatter, config: &mut Config, level: usize) -> fmt::Result {
out.write_str("[")?;
out.write_str("]")?;
self.inner.format(out, config, level + 1)?;
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Interface(());
impl TypeTrait for Interface {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Interface(self)
}
}
impl LangItem<Go> for Interface {
fn format(&self, out: &mut Formatter, _: &mut Config, _: usize) -> fmt::Result {
out.write_str("interface{}")
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Default)]
pub struct Config {
package: Option<Cons<'static>>,
}
impl Config {
pub fn with_package<P: Into<Cons<'static>>>(self, package: P) -> Self {
Self {
package: Some(package.into()),
..self
}
}
}
pub struct Go(());
impl Go {
fn imports<'el>(tokens: &Tokens<'el>) -> Option<Tokens<'el>> {
let mut modules = BTreeSet::new();
for custom in tokens.walk_custom() {
if let Some(import) = custom.as_import() {
import.type_imports(&mut modules);
}
}
if modules.is_empty() {
return None;
}
let mut out = Tokens::new();
for module in modules {
let mut s = Tokens::new();
s.append("import ");
s.append(module.quoted());
out.push(s);
}
Some(out)
}
}
impl Lang for Go {
type Config = Config;
type Import = dyn TypeTrait;
fn quote_string(out: &mut Formatter, input: &str) -> fmt::Result {
out.write_char('"')?;
for c in input.chars() {
match c {
'\t' => out.write_str("\\t")?,
'\n' => out.write_str("\\n")?,
'\r' => out.write_str("\\r")?,
'\'' => out.write_str("\\'")?,
'"' => out.write_str("\\\"")?,
'\\' => out.write_str("\\\\")?,
c => out.write_char(c)?,
};
}
out.write_char('"')?;
Ok(())
}
fn write_file(
tokens: Tokens<'_>,
out: &mut Formatter,
config: &mut Self::Config,
level: usize,
) -> fmt::Result {
let mut toks = Tokens::new();
if let Some(package) = &config.package {
toks.append("package");
toks.spacing();
toks.append(package.clone());
}
if let Some(imports) = Self::imports(&tokens) {
toks.line_spacing();
toks.push(imports);
}
toks.line_spacing();
toks.extend(tokens);
toks.format(out, config, level)
}
}
pub fn imported<M, N>(module: M, name: N) -> Type
where
M: Into<Cons<'static>>,
N: Into<Cons<'static>>,
{
Type {
module: Some(module.into()),
name: name.into(),
}
}
pub fn local<N>(name: N) -> Type
where
N: Into<Cons<'static>>,
{
Type {
module: None,
name: name.into(),
}
}
pub fn map<K, V>(key: K, value: V) -> Map
where
K: Into<TypeBox>,
V: Into<TypeBox>,
{
Map {
key: key.into(),
value: value.into(),
}
}
pub fn array<I>(inner: I) -> Array
where
I: Into<TypeBox>,
{
Array {
inner: inner.into(),
}
}
#[cfg(test)]
mod tests {
use super::{array, imported, map, Config, Go, Tokens, INTERFACE};
use crate as genco;
use crate::{quote, FormatterConfig, Quoted};
#[test]
fn test_string() {
let mut toks = Tokens::new();
toks.append("hello \n world".quoted());
let res = toks.to_string_with(
Config::default().with_package("foo"),
FormatterConfig::from_lang::<Go>(),
);
assert_eq!(Ok("\"hello \\n world\""), res.as_ref().map(|s| s.as_str()));
}
#[test]
fn test_imported() {
let dbg = imported("foo", "Debug");
let mut toks = Tokens::new();
toks.push(quote!(#dbg));
assert_eq!(
vec!["package foo", "", "import \"foo\"", "", "foo.Debug", ""],
toks.to_file_vec_with(
Config::default().with_package("foo"),
FormatterConfig::from_lang::<Go>()
)
.unwrap()
);
}
#[test]
fn test_map() {
let keyed = map(imported("foo", "Debug"), INTERFACE);
let mut toks = Tokens::new();
toks.push(quote!(#keyed));
assert_eq!(
vec![
"package foo",
"",
"import \"foo\"",
"",
"map[foo.Debug]interface{}",
""
],
toks.to_file_vec_with(
Config::default().with_package("foo"),
FormatterConfig::from_lang::<Go>()
)
.unwrap()
);
}
#[test]
fn test_array() {
let keyed = array(imported("foo", "Debug"));
let mut toks = Tokens::new();
toks.push(quote!(#keyed));
assert_eq!(
Ok("package foo\n\nimport \"foo\"\n\n[]foo.Debug\n"),
toks.to_file_string_with(
Config::default().with_package("foo"),
FormatterConfig::from_lang::<Go>()
)
.as_ref()
.map(|s| s.as_str())
);
}
}