add colors
This commit is contained in:
parent
2be25a330f
commit
4e9ea6c683
8 changed files with 82 additions and 78 deletions
1
.envrc
1
.envrc
|
@ -1 +0,0 @@
|
||||||
use flake
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,7 +1,11 @@
|
||||||
|
# build
|
||||||
/target
|
/target
|
||||||
/result
|
/result
|
||||||
|
|
||||||
.direnv
|
# environments
|
||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
|
# direnv
|
||||||
|
.envrc
|
||||||
|
.direnv
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -197,6 +197,7 @@ name = "vnetod"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
28
flake.nix
28
flake.nix
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: §ions.into_iter().map(String::from).collect::<Vec<_>>(),
|
sections: §ions.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]
|
||||||
|
|
Loading…
Reference in a new issue