diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d654666..678ebd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - rust: ["1.58", stable, nightly] + rust: ['1.66', stable, nightly] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index b11b1df..0eb4fa0 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - rust: ["1.58", stable] + rust: ['1.66', stable] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master diff --git a/Cargo.toml b/Cargo.toml index 8471ec9..3677a49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "genco" version = "0.17.8" authors = ["John-John Tedro "] edition = "2018" -rust-version = "1.58" +rust-version = "1.66" description = "A whitespace-aware quasiquoter for beautiful code generation." documentation = "https://docs.rs/genco" readme = "README.md" diff --git a/examples/nix.rs b/examples/nix.rs new file mode 100644 index 0000000..a7bd53b --- /dev/null +++ b/examples/nix.rs @@ -0,0 +1,34 @@ +use genco::fmt; +use genco::prelude::*; + +fn main() -> anyhow::Result<()> { + let nixpkgs = &nix::inherit("inputs", "nixpkgs"); + let pkgs = &nix::variable( + "pkgs", + quote! { + import $nixpkgs { + inherit ($nixpkgs) system; + config.allowUnfree = true; + overlays = []; + } + }, + ); + let mk_default = &nix::with("lib", "mkDefault"); + + let tokens = quote! { + { + imports = []; + environment.systemPackages = with $pkgs; []; + networking.useDHCP = $mk_default true; + } + }; + + let stdout = std::io::stdout(); + let mut w = fmt::IoWriter::new(stdout.lock()); + + let fmt = fmt::Config::from_lang::(); + let config = nix::Config::default(); + + tokens.format_file(&mut w.as_formatter(&fmt), &config)?; + Ok(()) +} diff --git a/genco-macros/Cargo.toml b/genco-macros/Cargo.toml index 3d48365..5eb1250 100644 --- a/genco-macros/Cargo.toml +++ b/genco-macros/Cargo.toml @@ -3,7 +3,7 @@ name = "genco-macros" version = "0.17.8" authors = ["John-John Tedro "] edition = "2018" -rust-version = "1.58" +rust-version = "1.66" description = """ A whitespace-aware quasiquoter for beautiful code generation. """ diff --git a/src/fmt/cursor.rs b/src/fmt/cursor.rs index 59346d7..942e64e 100644 --- a/src/fmt/cursor.rs +++ b/src/fmt/cursor.rs @@ -90,7 +90,7 @@ where where P: Parse, { - if let Some(item) = self.items.get(0) { + if let Some(item) = self.items.first() { P::peek(item) } else { false diff --git a/src/fmt/formatter.rs b/src/fmt/formatter.rs index cc9d4d9..8306cd7 100644 --- a/src/fmt/formatter.rs +++ b/src/fmt/formatter.rs @@ -13,14 +13,14 @@ static TABS: &str = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; #[derive(Debug, Clone, Copy)] -enum Line { +enum Whitespace { Initial, None, Push, Line, } -impl Line { +impl Whitespace { /// Convert into an indentation level. /// /// If we return `None`, no indentation nor lines should be written since we @@ -35,7 +35,7 @@ impl Line { } } -impl Default for Line { +impl Default for Whitespace { fn default() -> Self { Self::None } @@ -50,7 +50,7 @@ pub struct Formatter<'a> { /// How many lines we want to add to the output stream. /// /// This will only be realized if we push non-whitespace. - line: Line, + line: Whitespace, /// How many spaces we want to add to the output stream. /// /// This will only be realized if we push non-whitespace, and will be reset @@ -65,7 +65,7 @@ impl<'a> Formatter<'a> { pub(crate) fn new(write: &'a mut (dyn fmt::Write + 'a), config: &'a Config) -> Formatter<'a> { Formatter { write, - line: Line::Initial, + line: Whitespace::Initial, spaces: 0usize, indent: 0i16, config, @@ -90,7 +90,7 @@ impl<'a> Formatter<'a> { /// /// This will also reset any whitespace we have pending. pub(crate) fn write_trailing_line(&mut self) -> fmt::Result { - self.line = Line::default(); + self.line = Whitespace::default(); self.spaces = 0; self.write.write_trailing_line(self.config)?; Ok(()) @@ -108,9 +108,9 @@ impl<'a> Formatter<'a> { fn push(&mut self) { self.line = match self.line { - Line::Initial => return, - Line::Line => return, - _ => Line::Push, + Whitespace::Initial => return, + Whitespace::Line => return, + _ => Whitespace::Push, }; self.spaces = 0; @@ -119,8 +119,8 @@ impl<'a> Formatter<'a> { /// Push a new line. fn line(&mut self) { self.line = match self.line { - Line::Initial => return, - _ => Line::Line, + Whitespace::Initial => return, + _ => Whitespace::Line, }; self.spaces = 0; diff --git a/src/lang/mod.rs b/src/lang/mod.rs index a8205ea..3905d1e 100644 --- a/src/lang/mod.rs +++ b/src/lang/mod.rs @@ -23,6 +23,7 @@ pub mod dart; pub mod go; pub mod java; pub mod js; +pub mod nix; pub mod python; pub mod rust; pub mod swift; @@ -33,6 +34,7 @@ pub use self::dart::Dart; pub use self::go::Go; pub use self::java::Java; pub use self::js::JavaScript; +pub use self::nix::Nix; pub use self::python::Python; pub use self::rust::Rust; pub use self::swift::Swift; diff --git a/src/lang/nix.rs b/src/lang/nix.rs new file mode 100644 index 0000000..cf48058 --- /dev/null +++ b/src/lang/nix.rs @@ -0,0 +1,390 @@ +//! Nix +use crate as genco; +use crate::fmt; +use crate::quote_in; +use crate::tokens::ItemStr; +use std::collections::BTreeSet; +use std::fmt::Write as _; + +/// Tokens +pub type Tokens = crate::Tokens; + +impl_lang! { + /// Nix + pub Nix { + type Config = Config; + type Format = Format; + type Item = Import; + + 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 header = Tokens::new(); + + if !config.scoped { + Self::arguments(&mut header, tokens); + } + Self::withs(&mut header, tokens); + Self::imports(&mut header, tokens); + let format = Format::default(); + header.format(out, config, &format)?; + tokens.format(out, config, &format)?; + Ok(()) + } + } + + Import { + fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { + match self { + Import::Argument(import) => out.write_str(&import.0)?, + Import::Inherit(import) => out.write_str(&import.name)?, + Import::Variable(import) => out.write_str(&import.name)?, + Import::With(import) => out.write_str(&import.name)?, + } + Ok(()) + } + } +} + +/// Import +#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub enum Import { + /// Argument + Argument(ImportArgument), + /// Inherit + Inherit(ImportInherit), + /// Variable + Variable(ImportVariable), + /// With + With(ImportWith), +} + +/// ImportArgument +#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub struct ImportArgument(ItemStr); + +/// ImportInherit +#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub struct ImportInherit { + /// Path + path: ItemStr, + /// Name + name: ItemStr, +} + +/// ImportVariable +#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub struct ImportVariable { + /// Name + name: ItemStr, + /// Value + value: Tokens, +} + +/// ImportWith +#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub struct ImportWith { + /// Argument + argument: ItemStr, + /// Name + name: ItemStr, +} + +/// Format +#[derive(Debug, Default)] +pub struct Format {} + +/// Nix formatting configuration. +#[derive(Debug, Default)] +pub struct Config { + scoped: bool, +} + +impl Config { + /// With scoped + pub fn with_scoped(self, scoped: bool) -> Self { + Self { scoped } + } +} + +impl Nix { + fn arguments(out: &mut Tokens, tokens: &Tokens) { + let mut arguments = BTreeSet::new(); + + for imports in tokens.walk_imports() { + match imports { + Import::Argument(argument) => { + arguments.insert(argument.0.to_string()); + } + Import::Inherit(inherit) => { + let argument = inherit.path.split('.').next(); + if let Some(a) = argument { + arguments.insert(a.to_string()); + } + } + Import::Variable(variable) => { + let value = &variable.value; + for import in value.walk_imports() { + match import { + Import::Inherit(inherit) => { + let argument = inherit.path.split('.').next(); + if let Some(a) = argument { + arguments.insert(a.to_string()); + } + } + Import::Argument(argument) => { + arguments.insert(argument.0.to_string()); + } + _ => (), + } + } + } + Import::With(with) => { + arguments.insert(with.argument.to_string()); + } + } + } + + out.append("{"); + out.push(); + out.indent(); + + for argument in arguments { + quote_in!(*out => $argument,); + out.push(); + } + + out.append("..."); + out.push(); + + out.unindent(); + out.append("}:"); + out.push(); + + out.line(); + } + + fn withs(out: &mut Tokens, tokens: &Tokens) { + let mut withs = BTreeSet::new(); + + for imports in tokens.walk_imports() { + if let Import::With(with) = imports { + withs.insert(&with.argument); + } + } + + if withs.is_empty() { + return; + } + + for name in withs { + quote_in!(*out => with $name;); + out.push(); + } + + out.line(); + } + + fn imports(out: &mut Tokens, tokens: &Tokens) { + let mut inherits = BTreeSet::new(); + let mut variables = BTreeSet::new(); + + for imports in tokens.walk_imports() { + match imports { + Import::Inherit(inherit) => { + inherits.insert((&inherit.path, &inherit.name)); + } + Import::Variable(variable) => { + let value = &variable.value; + for import in value.walk_imports() { + if let Import::Inherit(inherit) = import { + inherits.insert((&inherit.path, &inherit.name)); + } + } + variables.insert((&variable.name, &variable.value)); + } + _ => (), + } + } + + if inherits.is_empty() && variables.is_empty() { + return; + } + + out.append("let"); + out.push(); + out.indent(); + + for (path, name) in inherits { + quote_in!(*out => inherit ($path) $name;); + out.push(); + } + + for (name, value) in variables { + quote_in!(*out => $name = $value;); + out.push(); + } + + out.unindent(); + out.append("in"); + out.push(); + + out.line(); + } +} + +/// ``` +/// use genco::prelude::*; +/// +/// let cell = nix::argument("cell"); +/// +/// let toks = quote! { +/// $cell +/// }; +/// +/// assert_eq!( +/// vec![ +/// "{", +/// " cell,", +/// " ...", +/// "}:", +/// "", +/// "cell", +/// ], +/// toks.to_file_vec()? +/// ); +/// # Ok::<_, genco::fmt::Error>(()) +/// ``` +pub fn argument(name: M) -> Import +where + M: Into, +{ + Import::Argument(ImportArgument(name.into())) +} + +/// ``` +/// use genco::prelude::*; +/// +/// let nixpkgs = nix::inherit("inputs", "nixpkgs"); +/// +/// let toks = quote! { +/// $nixpkgs +/// }; +/// +/// assert_eq!( +/// vec![ +/// "{", +/// " inputs,", +/// " ...", +/// "}:", +/// "", +/// "let", +/// " inherit (inputs) nixpkgs;", +/// "in", +/// "", +/// "nixpkgs", +/// ], +/// toks.to_file_vec()? +/// ); +/// # Ok::<_, genco::fmt::Error>(()) +/// ``` +pub fn inherit(path: M, name: N) -> Import +where + M: Into, + N: Into, +{ + Import::Inherit(ImportInherit { + path: path.into(), + name: name.into(), + }) +} + +/// ``` +/// use genco::prelude::*; +/// +/// let nixpkgs = &nix::inherit("inputs", "nixpkgs"); +/// +/// let pkgs = nix::variable("pkgs", quote! { +/// import $nixpkgs { +/// inherit ($nixpkgs) system; +/// config.allowUnfree = true; +/// } +/// }); +/// +/// let toks = quote! { +/// $pkgs +/// }; +/// +/// assert_eq!( +/// vec![ +/// "{", +/// " inputs,", +/// " ...", +/// "}:", +/// "", +/// "let", +/// " inherit (inputs) nixpkgs;", +/// " pkgs = import nixpkgs {", +/// " inherit (nixpkgs) system;", +/// " config.allowUnfree = true;", +/// " };", +/// "in", +/// "", +/// "pkgs" +/// ], +/// toks.to_file_vec()? +/// ); +/// # Ok::<_, genco::fmt::Error>(()) +/// ``` +pub fn variable(name: M, value: N) -> Import +where + M: Into, + N: Into, +{ + Import::Variable(ImportVariable { + name: name.into(), + value: value.into(), + }) +} + +/// ``` +/// use genco::prelude::*; +/// +/// let concat_map = nix::with("lib", "concatMap"); +/// let list_to_attrs = nix::with("lib", "listToAttrs"); +/// +/// let toks = quote! { +/// $list_to_attrs $concat_map +/// }; +/// +/// assert_eq!( +/// vec![ +/// "{", +/// " lib,", +/// " ...", +/// "}:", +/// "", +/// "with lib;", +/// "", +/// "listToAttrs concatMap", +/// ], +/// toks.to_file_vec()? +/// ); +/// # Ok::<_, genco::fmt::Error>(()) +/// ``` +pub fn with(argument: M, name: N) -> Import +where + M: Into, + N: Into, +{ + Import::With(ImportWith { + argument: argument.into(), + name: name.into(), + }) +}