add colors

This commit is contained in:
Dmitriy Pleshevskiy 2022-11-10 00:19:18 +03:00
parent 2be25a330f
commit 4e9ea6c683
Signed by: pleshevskiy
GPG key ID: 1B59187B161C0215
8 changed files with 82 additions and 78 deletions

1
.envrc
View file

@ -1 +0,0 @@
use flake

6
.gitignore vendored
View file

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

1
Cargo.lock generated
View file

@ -197,6 +197,7 @@ name = "vnetod"
version = "0.3.3" version = "0.3.3"
dependencies = [ dependencies = [
"clap", "clap",
"termcolor",
] ]
[[package]] [[package]]

View file

@ -13,6 +13,7 @@ categories = ["command-line-interface", "config", "development-tools"]
[dependencies] [dependencies]
clap = { version = "3.2.15", default-features = false, features = ["std", "env", "derive"] } clap = { version = "3.2.15", default-features = false, features = ["std", "env", "derive"] }
termcolor = { version = "1.1.3", optional = true }
[features] [features]
color = ["clap/color"] color = ["clap/color", "dep:termcolor"]

View file

@ -11,22 +11,28 @@
cargoToml = fromTOML (readFile ./Cargo.toml); cargoToml = fromTOML (readFile ./Cargo.toml);
version = "${cargoToml.package.version}+${substring 0 8 self.lastModifiedDate}.${self.shortRev or "dirty"}"; version = "${cargoToml.package.version}+${substring 0 8 self.lastModifiedDate}.${self.shortRev or "dirty"}";
mkVnetod = { lib, rustPlatform, ... }: mkVnetod = { lib, rustPlatform, vnetodFeatures ? [ ], ... }:
rustPlatform.buildRustPackage { rustPlatform.buildRustPackage {
name = "vnetod-${version}"; name = "vnetod-${version}";
src = lib.cleanSource ./.; src = lib.cleanSource ./.;
cargoLock.lockFile = ./Cargo.lock; cargoLock.lockFile = ./Cargo.lock;
buildFeatures = vnetodFeatures;
doCheck = true; doCheck = true;
}; };
in in
{ {
overlays = rec { overlays = {
vnetod = final: prev: { default = final: prev: {
vnetod = final.callPackage mkVnetod { }; vnetod = final.callPackage mkVnetod { };
}; };
default = vnetod; colored = final: prev: {
vnetod = final.callPackage mkVnetod {
vnetodFeatures = [ "color" ];
};
};
}; };
} }
// flake-utils.lib.eachDefaultSystem (system: // flake-utils.lib.eachDefaultSystem (system:
@ -34,6 +40,7 @@
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
vnetod = pkgs.callPackage mkVnetod { }; vnetod = pkgs.callPackage mkVnetod { };
coloredVnetod = pkgs.callPackage mkVnetod { vnetodFeatures = [ "color" ]; };
docker = pkgs.dockerTools.buildLayeredImage { docker = pkgs.dockerTools.buildLayeredImage {
name = "pleshevskiy/vnetod"; name = "pleshevskiy/vnetod";
@ -46,14 +53,21 @@
}; };
in in
{ {
apps.default = { apps = {
type = "app"; default = {
program = "${vnetod}/bin/vnetod"; type = "app";
program = "${vnetod}/bin/vnetod";
};
colored = {
type = "app";
program = "${coloredVnetod}/bin/vnetod";
};
}; };
packages = { packages = {
inherit docker vnetod; inherit docker vnetod;
default = vnetod; default = vnetod;
colored = coloredVnetod;
}; };
devShell = pkgs.mkShell { devShell = pkgs.mkShell {

View file

@ -14,21 +14,24 @@
//! 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, ColorChoice, ColorSpec, StandardStream, WriteColor};
use crate::{cli::Args, domain}; use crate::{cli::Args, domain};
use std::fs::File; use std::fs::File;
use std::io::{stdout, Write}; use std::io::Write;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
OpenFile, OpenFile,
Switch(domain::switch::Error), WriteFile,
} }
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::Switch(inner) => write!(f, "Cannot switch between states: {}", inner), Error::WriteFile => f.write_str("Cannot write file"),
} }
} }
} }
@ -38,20 +41,39 @@ 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)?;
let writer: Box<dyn Write> = if args.dry_run { let fs_writer = (!args.dry_run)
Box::new(stdout()) .then(|| {
} else { File::create(args.output.as_ref().unwrap_or(&args.file)).map_err(|_| Error::OpenFile)
Box::new( })
File::create(args.output.as_ref().unwrap_or(&args.file)) .transpose()?;
.map_err(|_| Error::OpenFile)?,
)
};
domain::switch::execute(domain::switch::Request { let new_content = domain::switch::execute(domain::switch::Request {
content: &content, content: &content,
writer,
sections: &args.sections, sections: &args.sections,
on_line: None, on_line: Some(Box::new(print_line)),
}) });
.map_err(Error::Switch) 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) {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
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();
}
#[cfg(not(feature = "color"))]
fn print_line(line: &String) {
print!("{}", line)
} }

View file

@ -44,7 +44,7 @@ impl Section {
} }
} }
pub(crate) struct SectionInfo { pub struct SectionInfo {
enable_variable: bool, enable_variable: bool,
disable_variable: bool, disable_variable: bool,
} }

View file

@ -14,43 +14,17 @@
//! 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};
#[derive(Debug)] pub type OnLineFn = Box<dyn Fn(&String)>;
pub enum Error {
WriteData,
}
impl std::fmt::Display for Error { pub struct Request<'args> {
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 type OnLineFn = Box<dyn Fn(Option<&SectionInfo>, &String)>;
pub struct Request<'args, W>
where
W: Write,
{
pub content: &'args str, pub content: &'args str,
pub writer: W,
pub sections: &'args [String], pub sections: &'args [String],
pub on_line: Option<OnLineFn>, pub on_line: Option<OnLineFn>,
} }
pub fn execute<W>(req: Request<W>) -> Result<(), Error> pub fn execute(req: Request) -> String {
where
W: Write,
{
let mut writer = BufWriter::new(req.writer);
let choose_sections = req let choose_sections = req
.sections .sections
.iter() .iter()
@ -58,6 +32,7 @@ where
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut current_section: Option<SectionInfo> = None; let mut current_section: 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) {
@ -86,16 +61,13 @@ where
line.to_string() line.to_string()
}; };
writer new_content.push_str(&new_line);
.write_all(new_line.as_bytes())
.map_err(|_| Error::WriteData)?;
if let Some(on_line) = req.on_line.as_ref() { if let Some(on_line) = req.on_line.as_ref() {
on_line(current_section.as_ref(), &new_line); on_line(&new_line);
} }
} }
writer.flush().map_err(|_| Error::WriteData) new_content
} }
fn is_variable(trimmed_line: &str) -> bool { fn is_variable(trimmed_line: &str) -> bool {
@ -151,28 +123,19 @@ 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 mut output_data = vec![]; let output_data = execute(Request {
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(()) => { });
let output = String::from_utf8(output_data).unwrap(); assert_eq!(
assert_eq!( output_data.lines().collect::<Vec<_>>(),
output.lines().collect::<Vec<_>>(), expected_output.lines().collect::<Vec<_>>()
expected_output.lines().collect::<Vec<_>>() );
);
}
_ => unreachable!(),
}
} }
#[test] #[test]