Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions debian-bin/dcps
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
/usr/bin/enderclitools dcps "$@"
56 changes: 56 additions & 0 deletions src/args/dcps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use crate::config::model::dcps::DcpsHeader;
use crate::config::model::table::{TableModifiers, TablePresets};
use clap::{Args, ValueEnum};
use std::fmt;

#[derive(Args, Debug, Clone)]
/// Pretty replacement for `docker compose ps`
pub struct DcpsArgs {
/// Show all containers (default shows just running)
#[arg(short, long)]
pub all: bool,
/// Filter output based on conditions provided
#[arg(short, long)]
pub filter: Option<String>,
/// Don't truncate output
#[arg(long)]
pub no_trunc: bool,
/// Exclude orphaned services (not declared by project)
#[arg(long)]
pub no_orphans: bool,
/// Only display container IDs
#[arg(short, long)]
pub quiet: bool,
/// Display services
#[arg(long)]
pub services: bool,
/// Filter services by status.
#[arg(long)]
pub status: Option<Vec<Status>>,
#[arg(long, value_enum)]
pub table_preset: Option<TablePresets>,
#[arg(long, value_enum)]
pub table_modifier: Option<TableModifiers>,
#[arg(long, value_enum)]
pub headers: Option<Vec<DcpsHeader>>,
#[arg(long, value_enum)]
pub add_headers: Option<Vec<DcpsHeader>>,
}

#[derive(Debug, Clone, ValueEnum, Copy)]
pub enum Status {
Paused,
Restarting,
Removing,
Running,
Dead,
Created,
Exited,
}

impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = format!("{:?}", self).to_ascii_lowercase();
write!(f, "{}", s)
}
}
3 changes: 3 additions & 0 deletions src/args/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::args::config::ConfigArgs;
use crate::args::dcps::DcpsArgs;
use crate::args::dps::DpsArgs;
use clap::{Parser, Subcommand};

pub mod config;
pub mod dcps;
pub mod dps;

#[derive(Parser, Debug)]
Expand All @@ -26,5 +28,6 @@ impl Cli {
#[derive(Subcommand, Debug, Clone)]
pub enum Commands {
Dps(DpsArgs),
Dcps(DcpsArgs),
Config(ConfigArgs),
}
74 changes: 74 additions & 0 deletions src/cmd/dcps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::args::dcps::DcpsArgs;
use crate::config::Config;
use crate::config::model::dcps::DcpsHeader;
use crate::utils;
use crate::utils::table::TableRow;
use anyhow::Result;

pub fn run(args: DcpsArgs) -> Result<()> {
let stdout = utils::docker::compose::ps(
args.all,
args.headers.as_deref(),
args.add_headers.as_deref(),
args.no_trunc,
args.no_orphans,
args.quiet,
args.services,
args.status.as_deref(),
)?;
let cfg = Config::load()?;
let table_preset = args.table_preset.unwrap_or(cfg.table.preset);
let table_modifier = args.table_modifier.unwrap_or(cfg.table.modifier);

let mut table_headers: TableRow = if args.quiet {
vec![DcpsHeader::Id.display_name().into()]
} else if args.services {
vec![DcpsHeader::Service.display_name().into()]
} else {
if let Some(headers) = args.headers {
headers
.into_iter()
.map(|h| h.display_name().into())
.collect()
} else {
cfg.dcps
.headers
.iter()
.map(|h| h.display_name().into())
.collect()
}
};

if let Some(add_headers) = args.add_headers {
for add_header in add_headers {
table_headers.push(add_header.display_name().into());
}
};

let mut table = utils::table::build_table(
&table_headers,
None,
Some(&table_preset),
Some(&table_modifier),
);

for line in stdout.lines() {
if line.trim().is_empty() {
continue;
}

let mut cols = line
.split(';')
.map(|s| s.trim().into())
.collect::<TableRow>();
while cols.len() < table_headers.len() {
cols.push("".into());
}

table.add_row(cols);
}

println!("{}", table);

Ok(())
}
1 change: 1 addition & 0 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod config;
pub mod dcps;
pub mod dps;
63 changes: 63 additions & 0 deletions src/config/model/dcps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DcpsConfig {
pub headers: Vec<DcpsHeader>,
}

impl Default for DcpsConfig {
fn default() -> DcpsConfig {
DcpsConfig {
headers: vec![
DcpsHeader::Service,
DcpsHeader::Image,
DcpsHeader::Status,
DcpsHeader::Ports,
],
}
}
}

#[derive(Debug, Serialize, Deserialize, Clone, Copy, ValueEnum)]
pub enum DcpsHeader {
Id,
Service,
Names,
Image,
Status,
Ports,
Command,
CreatedAt,
Created,
Size,
Labels,
Mounts,
}

impl fmt::Display for DcpsHeader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}

impl DcpsHeader {
pub fn display_name(&self) -> &str {
match self {
DcpsHeader::Id => "ID",
DcpsHeader::Service => "Service",
DcpsHeader::Names => "Names",
DcpsHeader::Image => "Image",
DcpsHeader::Status => "Status",
DcpsHeader::Ports => "Ports",
DcpsHeader::Command => "Command",
DcpsHeader::CreatedAt => "CreatedAt",
DcpsHeader::Created => "RunningFor",
DcpsHeader::Size => "Size",
DcpsHeader::Labels => "Labels",
DcpsHeader::Mounts => "Mounts",
}
}
}
1 change: 1 addition & 0 deletions src/config/model/dps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DpsConfig {
pub headers: Vec<DpsHeader>,
}
Expand Down
4 changes: 4 additions & 0 deletions src/config/model/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use crate::config::model::dcps::DcpsConfig;
use dps::DpsConfig;
use serde::{Deserialize, Serialize};
use table::TableConfig;

pub mod dcps;
pub mod dps;
pub mod table;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Config {
pub table: TableConfig,
pub dps: DpsConfig,
pub dcps: DcpsConfig,
}
1 change: 1 addition & 0 deletions src/config/model/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct TableConfig {
pub preset: TablePresets,
pub modifier: TableModifiers,
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ fn main() -> Result<()> {
args::Commands::Dps(opts) => {
cmd::dps::run(opts)?;
}
args::Commands::Dcps(opts) => {
cmd::dcps::run(opts)?;
}
args::Commands::Config(opts) => {
cmd::config::run(opts)?;
}
Expand Down
86 changes: 86 additions & 0 deletions src/utils/docker/compose.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use crate::args::dcps::Status;
use crate::config::Config;
use crate::config::model::dcps::DcpsHeader;
use anyhow::{Context, Result, bail};
use std::io;
use std::process::Command;

#[allow(clippy::too_many_arguments)]
pub fn ps(
all: bool,
headers: Option<&[DcpsHeader]>,
add_headers: Option<&[DcpsHeader]>,
no_trunc: bool,
no_orphans: bool,
quiet: bool,
services: bool,
status: Option<&[Status]>,
) -> Result<String> {
let mut args = vec!["compose".into(), "ps".to_string()];

if all {
args.push("--all".into());
}
if no_trunc {
args.push("--no-trunc".into());
}
if no_orphans {
args.push("--orphans=false".into());
}
if let Some(status) = status {
for s in status {
args.push("--status".into());
args.push(s.to_string());
}
}

args.push("--format".to_string());
let base_headers: Option<&[DcpsHeader]> = if quiet {
Some(&[DcpsHeader::Id])
} else if services {
Some(&[DcpsHeader::Service])
} else {
headers
};
let extra_headers = if quiet | services { None } else { add_headers };
args.push(get_headers(base_headers, extra_headers)?);

let attempt = Command::new("docker").args(&args).output();

match attempt {
Ok(out) if out.status.success() => Ok(String::from_utf8_lossy(&out.stdout).to_string()),
_ => {
if atty::is(atty::Stream::Stdin) {
bail!(
"failed to run `docker compose {}` and so STDIN provided",
args.join(" ")
)
}
let mut buf = String::new();
io::stdin().read_line(&mut buf).context("reading STDIN")?;
Ok(buf)
}
}
}

fn get_headers(
headers: Option<&[DcpsHeader]>,
add_headers: Option<&[DcpsHeader]>,
) -> Result<String> {
fn build(h: &[DcpsHeader], extra: Option<&[DcpsHeader]>) -> String {
h.iter()
.chain(extra.unwrap_or_default().iter())
.map(|hdr| format!("{{{{.{}}}}}", hdr.display_name()))
.collect::<Vec<_>>()
.join(";")
}

let result = if let Some(h) = headers {
build(h, add_headers)
} else {
let cfg = Config::load()?;
build(&cfg.dcps.headers, add_headers)
};

Ok(result)
}
2 changes: 2 additions & 0 deletions src/utils/docker.rs → src/utils/docker/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod compose;

use crate::config::Config;
use crate::config::model::dps::DpsHeader;
use anyhow::{Context, Result, bail};
Expand Down
2 changes: 2 additions & 0 deletions wix/alias/dcps.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
"%~dp0EnderCliTools.exe" dcps %*
4 changes: 4 additions & 0 deletions wix/main.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@
<Component Id='AliasDps' Guid='*'>
<File Id='AliasDps' Name='dps.cmd' DiskId='1' Source='wix\alias\dps.cmd'/>
</Component>
<Component Id='AliasDcps' Guid='*'>
<File Id='AliasDcps' Name='dps.cmd' DiskId='1' Source='wix\alias\dcps.cmd'/>
</Component>
</Directory>
</Directory>
</Directory>
Expand All @@ -154,6 +157,7 @@

<ComponentRef Id="AliasEct"/>
<ComponentRef Id="AliasDps"/>
<ComponentRef Id="AliasDcps"/>

<Feature
Id='Environment'
Expand Down
Loading