Compare commits

..

No commits in common. "main" and "v0.3.0" have entirely different histories.
main ... v0.3.0

15 changed files with 204 additions and 269 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
/target
.env*
!.envrc

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

7
.gitignore vendored
View file

@ -1,11 +1,4 @@
# build
/target /target
/result
# environments
.env* .env*
!.env.example !.env.example
# direnv
.envrc
.direnv

11
Cargo.lock generated
View file

@ -37,6 +37,7 @@ dependencies = [
"clap_lex", "clap_lex",
"indexmap", "indexmap",
"once_cell", "once_cell",
"strsim",
"termcolor", "termcolor",
"textwrap", "textwrap",
] ]
@ -154,6 +155,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.98"
@ -194,11 +201,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "vnetod" name = "vnetod"
version = "0.4.0" version = "0.2.2"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"termcolor",
] ]
[[package]] [[package]]

View file

@ -1,10 +1,9 @@
[package] [package]
name = "vnetod" name = "vnetod"
description = "Are you still switching sections in your dotenv file manually? Try this dotenv section switcher" description = "Dotenv state switcher"
version = "0.4.0" version = "0.3.0"
edition = "2021" edition = "2021"
license = "GPL-3.0+" license = "GPL-3.0+"
homepage = "https://github.com/pleshevskiy/vnetod/discussions"
repository = "https://git.pleshevski.ru/pleshevskiy/vnetod.git" repository = "https://git.pleshevski.ru/pleshevskiy/vnetod.git"
keywords = ["env", "dotenv", "switcher", "change"] keywords = ["env", "dotenv", "switcher", "change"]
categories = ["command-line-interface", "config", "development-tools"] categories = ["command-line-interface", "config", "development-tools"]
@ -12,9 +11,6 @@ categories = ["command-line-interface", "config", "development-tools"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = { version = "3.2.15", default-features = false, features = ["std", "env", "derive"] } clap = { version = "3.2.15", features = ["derive"] }
atty = { version = "0.2.14", optional = true }
termcolor = { version = "1.1.3", optional = true }
[features] [features]
color = ["clap/color", "dep:atty", "dep:termcolor"]

22
Dockerfile Normal file
View file

@ -0,0 +1,22 @@
FROM rust:1.62.0-slim-buster
WORKDIR /app
RUN cargo init .
COPY Cargo.* ./
RUN cargo build --release \
&& rm -rf src
COPY ./src ./src
RUN cargo install --bin vnetod --path . \
&& rm -rf ./src Cargo.*
VOLUME ["/data"]
WORKDIR /data
ENTRYPOINT ["/usr/local/cargo/bin/vnetod"]

View file

@ -1,20 +1,17 @@
# vnetod* # vnetod
<small><strong>*</strong> inverted word "dotenv"</small> Dotenv state switcher
Are you still switching sections in your dotenv file manually? Try this dotenv You can create many states in your `.env` and switch between them.
section switcher!
You can create many sections in your `.env` and switch between them.
Rules: Rules:
- Section name starts on a new line with `###` symbols (Ex. `### local`) - State name starts on a new line with `###` symbols (Ex. `### local`)
- Section can contain multiple comma-separated names (Ex. - State name can contain multiple comma-separated sections (Ex.
`### local,staging`) `### local,staging`)
- Each section name may specify a namespace (Ex. `### debug:on,dev:on`). If a - Each section may specify a namespace (Ex. `### debug:on,dev:on`). If a section
section doesn't contain a namespace, it's a global namespace. doesn't contain a namespace, it's a global namespace.
- Section ends if line is empty or contains a new section name. - State ends if line is empty or contains a new state name.
You can see the [full example]. You can see the [full example].
@ -85,13 +82,6 @@ docker run --rm -it -v $PWD:/data pleshevskiy/vnetod --help
nix run git+https://git.pleshevski.ru/pleshevskiy/vnetod -- --help nix run git+https://git.pleshevski.ru/pleshevskiy/vnetod -- --help
``` ```
# Contact me
- [send feedback](https://github.com/pleshevskiy/vnetod/discussions)
- [make an issue](https://github.com/pleshevskiy/vnetod/issues)
- matrix: @pleshevskiy:matrix.org
- email: dmitriy@pleshevski.ru
# License # License
GNU General Public License v3.0 or later GNU General Public License v3.0 or later

View file

@ -1,25 +1,11 @@
# Supported tags # Supported tags
- latest - latest
- 0.4 - 0.3
# vnetod* # vnetod
<small><strong>*</strong> inverted word "dotenv"</small> Dotenv state switcher
Are you still switching sections in your dotenv file manually? Try this dotenv
section switcher!
You can create many sections in your `.env` and switch between them.
Rules:
- Section name starts on a new line with `###` symbols (Ex. `### local`)
- Section can contain multiple comma-separated names (Ex.
`### local,staging`)
- Each section name may specify a namespace (Ex. `### debug:on,dev:on`). If a
section doesn't contain a namespace, it's a global namespace.
- Section ends if line is empty or contains a new section name.
# Usage # Usage

View file

@ -1,21 +1,39 @@
{ {
"nodes": { "nodes": {
"flake-utils": { "naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1655042882,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-9BX8Fuez5YJlN7cdPO63InoyBy7dm3VlJkkmTt6fS1A=",
"owner": "numtide", "owner": "nix-community",
"repo": "flake-utils", "repo": "naersk",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "rev": "cddffb5aa211f50c4b8750adbec0bbbdfb26bb9f",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "nix-community",
"repo": "flake-utils", "ref": "master",
"repo": "naersk",
"type": "github" "type": "github"
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": {
"lastModified": 1659190188,
"narHash": "sha256-LudYrDFPFaQMW0l68TYkPWRPKmqpxIFU1nWfylIp9AQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a3fddd46a7f3418d7e3940ded94701aba569161d",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1659190188, "lastModified": 1659190188,
"narHash": "sha256-LudYrDFPFaQMW0l68TYkPWRPKmqpxIFU1nWfylIp9AQ=", "narHash": "sha256-LudYrDFPFaQMW0l68TYkPWRPKmqpxIFU1nWfylIp9AQ=",
@ -33,8 +51,24 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "naersk": "naersk",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs_2",
"utils": "utils"
}
},
"utils": {
"locked": {
"lastModified": 1656928814,
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
} }
} }
}, },

View file

@ -1,77 +1,46 @@
{ {
inputs = { inputs = {
naersk.url = "github:nix-community/naersk/master";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
}; };
outputs = { self, nixpkgs, flake-utils }: outputs = { self, nixpkgs, utils, naersk }:
let utils.lib.eachDefaultSystem (system:
inherit (builtins) fromTOML readFile substring;
cargoToml = fromTOML (readFile ./Cargo.toml);
version = "${cargoToml.package.version}+${substring 0 8 self.lastModifiedDate}.${self.shortRev or "dirty"}";
mkVnetod = { lib, rustPlatform, vnetodFeatures ? [ ], ... }:
rustPlatform.buildRustPackage {
name = "vnetod-${version}";
src = lib.cleanSource ./.;
cargoLock.lockFile = ./Cargo.lock;
buildFeatures = vnetodFeatures;
doCheck = true;
};
in
{
overlays = {
minimal = final: prev: {
vnetod = final.callPackage mkVnetod { };
};
default = final: prev: {
vnetod = final.callPackage mkVnetod {
vnetodFeatures = [ "color" ];
};
};
};
}
// flake-utils.lib.eachDefaultSystem (system:
let let
name = "vnetod";
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
naersk-lib = pkgs.callPackage naersk { };
vnetod = pkgs.callPackage mkVnetod { vnetodFeatures = [ "color" ]; }; in
minimalVnetod = pkgs.callPackage mkVnetod { }; rec {
# Executes by `nix build .#<name>`
docker = pkgs.dockerTools.buildLayeredImage { packages = {
name = "pleshevskiy/vnetod"; ${name} = naersk.lib.${system}.buildPackage {
tag = cargoToml.package.version; pname = name;
config = { root = ./.;
Volumes."/data" = { };
WorkingDir = "/data";
Entrypoint = [ "${vnetod}/bin/vnetod" ];
}; };
}; };
# Executes by `nix build .`
packages.default = packages.${name};
# the same but deprecated in Nix 2.7
defaultPackage = packages.default;
mkApp = prog: { # Executes by `nix run .#<name> -- <args?>`
type = "app";
program = "${vnetod}/bin/vnetod";
};
in
{
apps = { apps = {
default = mkApp vnetod; ${name} = utils.lib.mkApp {
minimal = mkApp minimalVnetod; inherit name;
drv = packages.${name};
};
}; };
# Executes by `nix run . -- <args?>`
apps.default = apps.${name};
# the same but deprecated in Nix 2.7
defaultApp = apps.default;
packages = { # Used by `nix develop`
inherit docker vnetod; devShell = with pkgs; mkShell {
default = vnetod; buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy ];
minimal = minimalVnetod; RUST_SRC_PATH = rustPlatform.rustLibSrc;
};
devShell = pkgs.mkShell {
packages = with pkgs; [ cargo rustc rustfmt clippy rust-analyzer ];
RUST_SRC_PATH = pkgs.rustPlatform.rustLibSrc;
}; };
}); });
} }

View file

@ -1,4 +1,4 @@
//! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@pleshevski.ru> //! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@ideascup.me>
//! //!
//! vnetod is free software: you can redistribute it and/or modify //! vnetod is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by //! it under the terms of the GNU General Public License as published by
@ -20,14 +20,14 @@ use std::path::PathBuf;
use clap::Parser; use clap::Parser;
#[derive(Parser, Debug)] #[derive(Parser)]
#[clap( #[clap(
author, author,
version, version,
about = "\ about = "\
Dotenv state switcher Dotenv state switcher
--------------------------------------------------------------------- ---------------------------------------------------------------------
vnetod Copyright (C) 2022 Dmitriy Pleshevskiy <dmitriy@pleshevski.ru> vnetod Copyright (C) 2022 Dmitriy Pleshevskiy <dmitriy@ideascup.me>
This program comes with ABSOLUTELY NO WARRANTY; This program comes with ABSOLUTELY NO WARRANTY;
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; under certain conditions;
@ -39,15 +39,14 @@ pub struct Args {
short = 'f', short = 'f',
long, long,
default_value = ".env", default_value = ".env",
env = "VNETOD_FILE", help = "Change source file with environment variables"
help = "Change source file with environment variables."
)] )]
pub file: PathBuf, pub file: PathBuf,
#[clap( #[clap(
short = 'o', short = 'o',
long, long,
help = "Change output file with modified environment variables. It uses `file` argument by default if the output is not specified." help = "Change output file with modified environment variables. It uses `file` argument by default if the output is not specified"
)] )]
pub output: Option<PathBuf>, pub output: Option<PathBuf>,
@ -59,66 +58,7 @@ pub struct Args {
#[clap( #[clap(
value_parser, value_parser,
help = "Environment varible sections that will be enabled." help = "Environment varible sections that will be enabled"
)] )]
pub sections: Vec<String>, pub sections: Vec<String>,
#[cfg(feature = "color")]
#[clap(
long,
value_enum,
default_value = "auto",
help = "This flag controls when to use colors.",
long_help = "
This flag controls when to use colors. The default setting is 'auto', which
means vnetod will try to guess when to use colors. For example, if vnetod is
printing to a terminal, then it will use colors, but if it is redirected to a
file or a pipe, then it will suppress color output. vnetod will suppress color
output in some other circumstances as well. For example, if the TERM
environment variable is not set or set to 'dumb', then vnetod will not use
colors.
The possible values for this flag are:
never Colors will never be used.
auto The default. vnetod tries to be smart.
always Colors will always be used regardless of where output is sent.
ansi Like 'always', but emits ANSI escapes (even in a Windows console).
"
)]
pub color: ColorVariant,
}
#[cfg(feature = "color")]
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum ColorVariant {
Auto,
Always,
Ansi,
Never,
}
#[cfg(feature = "color")]
impl From<ColorVariant> for termcolor::ColorChoice {
fn from(col: ColorVariant) -> Self {
match col {
ColorVariant::Never => Self::Never,
ColorVariant::Always => Self::Always,
ColorVariant::Ansi => Self::AlwaysAnsi,
ColorVariant::Auto => {
if atty::is(atty::Stream::Stdout) {
// Otherwise let's `termcolor` decide by inspecting the environment. From the [doc]:
// - If `NO_COLOR` is set to any value, then colors will be suppressed.
// - If `TERM` is set to dumb, then colors will be suppressed.
// - In non-Windows environments, if `TERM` is not set, then colors will be suppressed.
//
// [doc]: https://github.com/BurntSushi/termcolor#automatic-color-selection
Self::Auto
} else {
// Colors should be deactivated if the terminal is not a tty.
Self::Never
}
}
}
}
} }

View file

@ -1,4 +1,4 @@
//! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@pleshevski.ru> //! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@ideascup.me>
//! //!
//! vnetod is free software: you can redistribute it and/or modify //! vnetod is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by //! it under the terms of the GNU General Public License as published by
@ -14,24 +14,21 @@
//! along with vnetod. If not, see <https://www.gnu.org/licenses/>. //! along with vnetod. If not, see <https://www.gnu.org/licenses/>.
//! //!
#[cfg(feature = "color")]
use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
use crate::{cli::Args, domain}; use crate::{cli::Args, domain};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::{stdout, Write};
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
OpenFile, OpenFile,
WriteFile, Switch(domain::switch::Error),
} }
impl std::fmt::Display for Error { impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Error::OpenFile => f.write_str("Cannot open file"), Error::OpenFile => f.write_str("Cannot open file"),
Error::WriteFile => f.write_str("Cannot write file"), Error::Switch(inner) => write!(f, "Cannot switch between states: {}", inner),
} }
} }
} }
@ -41,51 +38,19 @@ impl std::error::Error for Error {}
pub fn execute(args: &Args) -> Result<(), Error> { pub fn execute(args: &Args) -> Result<(), Error> {
let content = std::fs::read_to_string(&args.file).map_err(|_| Error::OpenFile)?; let content = std::fs::read_to_string(&args.file).map_err(|_| Error::OpenFile)?;
if args.dry_run { let writer: Box<dyn Write> = if args.dry_run {
println!("Your file will be changed to the following") Box::new(stdout())
} } else {
Box::new(
File::create(args.output.as_ref().unwrap_or(&args.file))
.map_err(|_| Error::OpenFile)?,
)
};
let fs_writer = (!args.dry_run) domain::switch::execute(domain::switch::Request {
.then(|| { content: &content,
File::create(args.output.as_ref().unwrap_or(&args.file)).map_err(|_| Error::OpenFile) writer,
})
.transpose()?;
#[cfg(feature = "color")]
let color = args.color.clone();
println!();
let new_content = domain::switch::execute(domain::switch::Request {
content: &content.trim(),
sections: &args.sections, sections: &args.sections,
on_line: Some(Box::new(move |line| { })
#[cfg(feature = "color")] .map_err(Error::Switch)
print_line(line, color.clone());
#[cfg(not(feature = "color"))]
print!("{}", line)
})),
});
println!();
if let Some(mut fs_writer) = fs_writer {
fs_writer
.write_all(new_content.as_bytes())
.map_err(|_| Error::WriteFile)?;
}
Ok(())
}
#[cfg(feature = "color")]
fn print_line(line: &String, color: crate::cli::ColorVariant) {
let mut stdout = StandardStream::stdout(color.into());
let color = line
.starts_with("###")
.then_some(Color::Yellow)
.or_else(|| (!line.starts_with("#")).then_some(Color::Green));
stdout.set_color(ColorSpec::new().set_fg(color)).ok();
write!(&mut stdout, "{}", line).unwrap();
stdout.reset().ok();
} }

View file

@ -1,4 +1,4 @@
//! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@pleshevski.ru> //! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@ideascup.me>
//! //!
//! vnetod is free software: you can redistribute it and/or modify //! vnetod is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by //! it under the terms of the GNU General Public License as published by
@ -44,7 +44,7 @@ impl Section {
} }
} }
pub struct SectionInfo { struct SectionInfo {
enable_variable: bool, enable_variable: bool,
disable_variable: bool, disable_variable: bool,
} }

View file

@ -1,4 +1,4 @@
//! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@pleshevski.ru> //! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@ideascup.me>
//! //!
//! vnetod is free software: you can redistribute it and/or modify //! vnetod is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by //! it under the terms of the GNU General Public License as published by
@ -14,32 +14,54 @@
//! along with vnetod. If not, see <https://www.gnu.org/licenses/>. //! along with vnetod. If not, see <https://www.gnu.org/licenses/>.
//! //!
use std::io::{BufWriter, Write};
use super::{Section, SectionInfo}; use super::{Section, SectionInfo};
pub type OnLineFn = Box<dyn Fn(&String)>; #[derive(Debug)]
pub enum Error {
pub struct Request<'args> { WriteData,
pub content: &'args str,
pub sections: &'args [String],
pub on_line: Option<OnLineFn>,
} }
pub fn execute(req: Request) -> String { impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::WriteData => f.write_str("Cannot write data"),
}
}
}
impl std::error::Error for Error {}
pub struct Request<'args, W>
where
W: Write,
{
pub content: &'args str,
pub writer: W,
pub sections: &'args [String],
}
pub fn execute<W>(req: Request<W>) -> Result<(), Error>
where
W: Write,
{
let mut writer = BufWriter::new(req.writer);
let choose_sections = req let choose_sections = req
.sections .sections
.iter() .iter()
.map(|s| Section::parse(s.as_str())) .map(|s| Section::parse(s.as_str()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut current_section: Option<SectionInfo> = None; let mut current_sections: Option<SectionInfo> = None;
let mut new_content = String::new();
for line in req.content.split_inclusive('\n') { for line in req.content.split_inclusive('\n') {
let new_line = if is_section_end(line) { let new_line = if is_section_end(line) {
current_section = None; current_sections = None;
line.to_string() line.to_string()
} else if let Some(section_info) = line.strip_prefix("### ") { } else if let Some(section_info) = line.strip_prefix("### ") {
current_section = section_info.split_whitespace().next().map(|r| { current_sections = section_info.split_whitespace().next().map(|r| {
let current_sections = r.split(',').map(Section::parse).collect::<Vec<_>>(); let current_sections = r.split(',').map(Section::parse).collect::<Vec<_>>();
SectionInfo { SectionInfo {
enable_variable: should_enable_variable(&choose_sections, &current_sections), enable_variable: should_enable_variable(&choose_sections, &current_sections),
@ -47,7 +69,7 @@ pub fn execute(req: Request) -> String {
} }
}); });
line.to_string() line.to_string()
} else if let Some(section_info) = current_section.as_ref() { } else if let Some(section_info) = current_sections.as_ref() {
let trimmed_line = line.trim_start_matches(['#', ' ']); let trimmed_line = line.trim_start_matches(['#', ' ']);
let is_var = is_variable(trimmed_line); let is_var = is_variable(trimmed_line);
if is_var && section_info.enable_variable { if is_var && section_info.enable_variable {
@ -61,13 +83,12 @@ pub fn execute(req: Request) -> String {
line.to_string() line.to_string()
}; };
new_content.push_str(&new_line); writer
if let Some(on_line) = req.on_line.as_ref() { .write_all(new_line.as_bytes())
on_line(&new_line); .map_err(|_| Error::WriteData)?;
}
} }
new_content writer.flush().map_err(|_| Error::WriteData)
} }
fn is_variable(trimmed_line: &str) -> bool { fn is_variable(trimmed_line: &str) -> bool {
@ -123,19 +144,28 @@ fn should_disable_variable(choose_sections: &[Section], current_sections: &[Sect
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::io::Cursor;
const BASE_ENV: &str = include_str!("../../test_data/base_env"); const BASE_ENV: &str = include_str!("../../test_data/base_env");
fn make_test(input: &str, expected_output: &str, sections: Vec<&str>) { fn make_test(input: &str, expected_output: &str, sections: Vec<&str>) {
let output_data = execute(Request { let mut output_data = vec![];
let writer = Cursor::new(&mut output_data);
match execute(Request {
content: input, content: input,
writer,
sections: &sections.into_iter().map(String::from).collect::<Vec<_>>(), sections: &sections.into_iter().map(String::from).collect::<Vec<_>>(),
on_line: None, }) {
}); Ok(()) => {
assert_eq!( let output = String::from_utf8(output_data).unwrap();
output_data.lines().collect::<Vec<_>>(), assert_eq!(
expected_output.lines().collect::<Vec<_>>() output.lines().collect::<Vec<_>>(),
); expected_output.lines().collect::<Vec<_>>()
);
}
_ => unreachable!(),
}
} }
#[test] #[test]

View file

@ -1,4 +1,4 @@
//! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@pleshevski.ru> //! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@ideascup.me>
//! //!
//! vnetod is free software: you can redistribute it and/or modify //! vnetod is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by //! it under the terms of the GNU General Public License as published by