diff --git a/Cargo.lock b/Cargo.lock
index 1ea850b..8bfd005 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -32,6 +32,56 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "anyhow"
version = "1.0.103"
@@ -176,6 +226,46 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+[[package]]
+name = "clap"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.4",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
[[package]]
name = "color-eyre"
version = "0.6.5"
@@ -203,6 +293,12 @@ dependencies = [
"tracing-error",
]
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
[[package]]
name = "compact_str"
version = "0.9.1"
@@ -695,6 +791,12 @@ dependencies = [
"syn 2.0.118",
]
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
[[package]]
name = "itertools"
version = "0.14.0"
@@ -1016,6 +1118,12 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
[[package]]
name = "ordered-float"
version = "4.6.0"
@@ -1227,6 +1335,7 @@ version = "0.0.5"
dependencies = [
"anyhow",
"bytes",
+ "clap",
"color-eyre",
"crossterm 0.28.1",
"futures",
@@ -1670,6 +1779,17 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "syn"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "sysinfo"
version = "0.39.5"
diff --git a/Cargo.toml b/Cargo.toml
index 535f4a3..bffd488 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ tui-term = "0.3.4"
bytes = "1.8.0"
portable-pty = "0.9.0"
sysinfo = "0.39.5"
+clap = { version = "4.6.6", features = ["derive"] }
[target.'cfg(unix)'.dependencies]
nix = { version = "0.29.0", features = ["signal"] }
diff --git a/src/app.rs b/src/app.rs
index 1f31007..a160603 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -12,6 +12,7 @@
//! along with this program. If not, see .
use crate::event::{AppEvent, Event, EventHandler};
+use crate::formation::Formation;
use crate::process::Process;
use crate::procfile;
use anyhow::Result;
@@ -66,12 +67,34 @@ const COLORS: &[Color] = &[
];
impl App {
- pub fn new(procfile_path: String) -> Self {
+ pub fn new(procfile_path: String, formation: Option) -> Self {
let entries = procfile::parse(&procfile_path).unwrap_or_default();
+
+ // Expand each Procfile entry according to the formation:
+ // count=0 → skip the process entirely
+ // count=1 → single instance, keep the original name
+ // count>1 → numbered instances: name.1, name.2, …
let processes = entries
.into_iter()
+ .flat_map(|e| {
+ let count = formation
+ .as_ref()
+ .map(|f| f.count_for(&e.name))
+ .unwrap_or(1);
+
+ (1..=count)
+ .map(|n| {
+ let name = if count == 1 {
+ e.name.clone()
+ } else {
+ format!("{}.{}", e.name, n)
+ };
+ (name, e.command.clone())
+ })
+ .collect::>()
+ })
.enumerate()
- .map(|(i, e)| Process::new(e.name, e.command, COLORS[i % COLORS.len()]))
+ .map(|(i, (name, command))| Process::new(name, command, COLORS[i % COLORS.len()]))
.collect();
Self {
diff --git a/src/formation.rs b/src/formation.rs
new file mode 100644
index 0000000..225ce40
--- /dev/null
+++ b/src/formation.rs
@@ -0,0 +1,48 @@
+//! This program is free software: you can redistribute it and/or modify
+//! it under the terms of the GNU General Public License as published by
+//! the Free Software Foundation, either version 3 of the License, or
+//! (at your option) any later version.
+//!
+//! This program is distributed in the hope that it will be useful,
+//! but WITHOUT ANY WARRANTY; without even the implied warranty of
+//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//! GNU General Public License for more details.
+//!
+//! You should have received a copy of the GNU General Public License
+//! along with this program. If not, see .
+
+use std::collections::HashMap;
+
+/// Maps process names to their desired instance counts.
+/// Supports an `all` wildcard that acts as the default for any unlisted process.
+pub struct Formation(pub HashMap);
+
+impl From for Formation {
+ fn from(s: String) -> Self {
+ let map = s
+ .split(',')
+ .filter_map(|part| {
+ let mut kv = part.splitn(2, '=');
+ let key = kv.next()?.trim().to_string();
+ let val: usize = kv.next()?.trim().parse().ok()?;
+ Some((key, val))
+ })
+ .collect();
+ Self(map)
+ }
+}
+
+impl Formation {
+ /// Returns the number of instances to run for a given process name.
+ /// Falls back to the `all` entry if the name is not explicitly listed,
+ /// and defaults to 1 if neither is present.
+ pub fn count_for(&self, name: &str) -> usize {
+ if let Some(&n) = self.0.get(name) {
+ n
+ } else if let Some(&n) = self.0.get("all") {
+ n
+ } else {
+ 1
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index b5bdee9..94daabc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,17 +13,40 @@
pub mod app;
pub mod event;
+pub mod formation;
pub mod process;
pub mod procfile;
pub mod ui;
+use clap::Parser;
+use formation::Formation;
+use std::path::PathBuf;
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+struct Args {
+ /// Specify an alternate location for the application's Procfile.
+ #[arg(short, long)]
+ procfile: Option,
+
+ /// Specify the number of each process type to run. The value passed in should be in the format process=num,process=num
+ #[arg(short = 'm', long)]
+ formation: Option,
+}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
- let procfile_path = std::env::args()
- .nth(1)
- .unwrap_or_else(|| "Procfile".to_string());
+ let args = Args::parse();
+
+ let formation = args.formation.map(Formation::from);
+
+ let procfile_path = if let Some(procfile) = args.procfile {
+ String::from(procfile.to_str().unwrap())
+ } else {
+ String::from("Procfile")
+ };
+
let terminal = ratatui::init();
- let result = app::App::new(procfile_path).run(terminal).await;
+ let result = app::App::new(procfile_path, formation).run(terminal).await;
ratatui::restore();
result
}