Skip to content

Commit d0f07b4

Browse files
committed
refactor(core): TUI steps to use pipeline pattern.
1 parent 94c92ed commit d0f07b4

19 files changed

Lines changed: 565 additions & 719 deletions

File tree

Cargo.lock

Lines changed: 153 additions & 220 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,23 @@
22
name = "simple-commit"
33
description = "A little CLI written in rust to improve your dirty commits into conventional ones. "
44
repository = "https://github.com/romancitodev/simple-commits"
5-
authors = ["romancitodev"]
65
version = "1.0.2"
76
edition = "2021"
87
license = "MIT"
98

10-
[[bin]]
11-
name = "sc"
12-
path = "src/main.rs"
13-
149
[package.metadata.wix]
1510
upgrade-guid = "C20E12CB-E616-45DF-8B01-11541D65C6CE"
1611
path-guid = "E4D36954-1F3E-4535-A0BA-B439FA323671"
1712
license = false
1813
eula = false
1914

15+
[[bin]]
16+
name = "sc"
17+
path = "src/main.rs"
18+
2019
[dependencies]
2120
thiserror = "1"
22-
cliclack = "0.3.4"
21+
cliclack = "0.3.7"
2322
clap = { version = "4.5.3", features = ["derive"] }
2423
clap_derive = { version = "4.0.0-rc.1" }
2524
colored = "2.1.0"

src/config/cli.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct CliConfig {
1414
pub(super) config: Option<PathBuf>,
1515

1616
#[clap(flatten)]
17-
pub(super) sc_config: SimpleCommitsConfig,
17+
pub(super) sc_config: AppConfig,
1818

1919
#[command(subcommand)]
2020
pub(super) mode: Option<Command>,
@@ -34,7 +34,7 @@ pub enum InitOptions {
3434

3535
/// File settings for customizing the bin.
3636
#[derive(Debug, Default, Serialize, Deserialize, Parser, Merge)]
37-
pub struct SimpleCommitsConfig {
37+
pub struct AppConfig {
3838
#[merge(skip)]
3939
#[serde(skip)]
4040
#[clap(skip)]
@@ -54,7 +54,7 @@ pub struct SimpleCommitsConfig {
5454
pub git: Option<GitConfig>,
5555
}
5656

57-
impl SimpleCommitsConfig {
57+
impl AppConfig {
5858
pub fn update(&self) -> std::io::Result<()> {
5959
let updated = toml::to_string_pretty(&self).unwrap();
6060
fs::write(&self.config, updated)?;

src/config/helpers.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::tui::structs::Scope;
22

3-
use super::cli::{InitOptions, SimpleCommitsConfig};
3+
use super::cli::{AppConfig, InitOptions};
44
use directories::BaseDirs;
55
use merge2::Merge;
66
use std::{env::current_dir, path::PathBuf};
@@ -36,11 +36,11 @@ pub fn create_config(option: InitOptions) -> PathBuf {
3636
/// (Global, Local)
3737
type ConfigPaths = (PathBuf, Option<PathBuf>);
3838

39-
pub fn load_config(path: Option<PathBuf>, config: &mut SimpleCommitsConfig) -> ConfigPaths {
39+
pub fn load_config(path: Option<PathBuf>, config: &mut AppConfig) -> ConfigPaths {
4040
let global_path = path.unwrap_or(create_config(InitOptions::Global));
4141

4242
if let Ok(content) = std::fs::read_to_string(&global_path) {
43-
let mut global_config: SimpleCommitsConfig = toml::from_str(&content).unwrap();
43+
let mut global_config: AppConfig = toml::from_str(&content).unwrap();
4444
config.config.clone_from(&global_path);
4545
config.scopes = Some(Scope::default());
4646
config.merge(&mut global_config);
@@ -50,7 +50,7 @@ pub fn load_config(path: Option<PathBuf>, config: &mut SimpleCommitsConfig) -> C
5050

5151
if let Ok(local_path_ok) = &local_path {
5252
if let Ok(content) = std::fs::read_to_string(local_path_ok) {
53-
let mut local_config: SimpleCommitsConfig = toml::from_str(&content).unwrap();
53+
let mut local_config: AppConfig = toml::from_str(&content).unwrap();
5454
config.config.clone_from(local_path_ok);
5555
config.scopes = Some(Scope::default());
5656
config.merge(&mut local_config);

src/config/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ pub mod cli;
66
pub mod git;
77
pub mod helpers;
88

9-
pub fn get_config() -> (cli::SimpleCommitsConfig, Option<Command>) {
9+
pub fn get_config() -> (cli::AppConfig, Option<Command>) {
1010
let mut args = cli::CliConfig::parse();
11-
let mut config = cli::SimpleCommitsConfig::default();
11+
let mut config = cli::AppConfig::default();
1212

1313
if let Some(Command::Init(option)) = args.mode {
1414
let path = helpers::create_config(option);

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod config;
22
pub mod errors;
33
mod gen;
4+
mod reimpl;
45
mod tui;
56

67
pub fn main() {

src/reimpl.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! Pipeline pattern for composing commit creation steps.
2+
//!
3+
//! This module provides a functional, composable approach to building commits
4+
//! through a series of steps. Each step receives mutable access to the pipeline
5+
//! and can modify the state and configuration as needed.
6+
//!
7+
//! # Example
8+
//!
9+
//! ```rust,ignore
10+
//! Pipeline::new(config)
11+
//! .then(|pipeline| {
12+
//! // Step 1: Do something with pipeline.state and pipeline.config
13+
//! Ok(())
14+
//! })?
15+
//! .then(|pipeline| {
16+
//! // Step 2: Next operation
17+
//! Ok(())
18+
//! })?;
19+
//! ```
20+
//!
21+
//! The pipeline pattern provides:
22+
//! - **Explicit data flow**: State and config are passed through each step
23+
//! - **Error propagation**: Each step returns `Result<(), AppError>`
24+
//! - **Composability**: Steps can be chained with `.then()`
25+
//! - **Type safety**: The compiler ensures proper error handling
26+
27+
use crate::{config::cli::AppConfig, errors::AppError, tui::AppData};
28+
29+
/// The pipeline that carries state and configuration through commit creation steps.
30+
///
31+
/// Each step in the pipeline receives mutable access to this struct and can:
32+
/// - Read and modify the application state (`state`)
33+
/// - Read and modify the configuration (`config`)
34+
/// - Return an error to halt the pipeline
35+
pub struct Pipeline {
36+
/// The application state, primarily containing the commit being built.
37+
pub state: AppData,
38+
/// The application configuration, including user preferences and settings.
39+
pub config: AppConfig,
40+
}
41+
42+
impl Pipeline {
43+
/// Creates a new pipeline with the given configuration.
44+
///
45+
/// The state is initialized to its default value.
46+
pub fn new(config: AppConfig) -> Self {
47+
Self {
48+
state: AppData::default(),
49+
config,
50+
}
51+
}
52+
}
53+
54+
impl Pipeline {
55+
/// Chains a step onto the pipeline.
56+
///
57+
/// The provided closure receives mutable access to the pipeline and can modify
58+
/// both the state and configuration. If the closure returns an error, the pipeline
59+
/// stops and the error is propagated.
60+
///
61+
/// # Arguments
62+
/// * `run` - A closure that performs the step's logic
63+
///
64+
/// # Returns
65+
/// * `Ok(Pipeline)` - The pipeline to continue chaining
66+
/// * `Err(AppError)` - If the step encountered an error
67+
///
68+
/// # Example
69+
///
70+
/// ```rust,ignore
71+
/// pipeline.then(|p| {
72+
/// p.state.commit.set_title(Some("feat: add feature".to_string()));
73+
/// Ok(())
74+
/// })?
75+
/// ```
76+
pub fn then<F>(mut self, run: F) -> Result<Pipeline, AppError>
77+
where
78+
F: FnOnce(&mut Self) -> Result<(), AppError>,
79+
{
80+
run(&mut self)?;
81+
Ok(self)
82+
}
83+
84+
pub fn if_then<F, D>(mut self, cond: D, run: F) -> Result<Pipeline, AppError>
85+
where
86+
D: FnOnce(&mut Self) -> Result<bool, AppError>,
87+
F: FnOnce(&mut Self) -> Result<(), AppError>,
88+
{
89+
if cond(&mut self)? {
90+
run(&mut self)?;
91+
}
92+
Ok(self)
93+
}
94+
}

src/tui/config_prompt/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
use crate::config::cli::SimpleCommitsConfig;
1+
use crate::config::cli::AppConfig;
22
use cliclack::{
33
intro,
44
log::{info, step},
55
outro,
66
};
77

8-
pub fn init(
9-
SimpleCommitsConfig { config, .. }: &mut SimpleCommitsConfig,
10-
) -> Result<(), std::io::Error> {
8+
pub fn init(AppConfig { config, .. }: AppConfig) -> Result<(), std::io::Error> {
119
intro("Simple Commit")?;
1210

1311
step("Setting up configuration files")?;

src/tui/helpers.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
pub const BLANK_CHARACTER: &str = "";
2-
31
pub fn valid_length(text: &str, min: usize, msg: &str) -> Result<(), String> {
42
if text.len() > min {
53
Ok(())

src/tui/mod.rs

Lines changed: 19 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use crate::{
2-
config::{cli::SimpleCommitsConfig, get_config},
3-
errors::AppError,
4-
};
1+
use crate::config::get_config;
52

63
pub mod config_prompt;
74
pub mod helpers;
@@ -11,13 +8,13 @@ pub mod widgets;
118

129
/// initialize the configuration and setup the steps
1310
pub fn init() {
14-
let (mut config, command) = get_config();
11+
let (config, command) = get_config();
1512
match command {
1613
Some(_) => {
17-
_ = config_prompt::init(&mut config);
14+
_ = config_prompt::init(config);
1815
}
1916
None => {
20-
_ = steps::init(&mut config);
17+
_ = steps::init(config);
2118
}
2219
}
2320
}
@@ -27,65 +24,16 @@ pub struct AppData {
2724
pub commit: CommitBuilder,
2825
}
2926

30-
#[allow(dead_code)]
31-
#[derive(Clone, Debug, Default)]
32-
pub enum Action {
33-
#[default]
34-
None,
35-
DryRun(String),
36-
Commit(String, Vec<String>),
37-
}
38-
39-
impl Action {
40-
/// Returns the action to be executed of this [`Action`].
41-
pub fn execute_action(&self) {
42-
match self {
43-
Self::DryRun(msg) => println!("{msg}"),
44-
Self::Commit(cmd, args) => {
45-
let _ = std::process::Command::new(cmd)
46-
.args(&args[..])
47-
.spawn()
48-
.expect("The child failed for some reason")
49-
.wait();
50-
}
51-
Self::None => {}
52-
}
53-
}
54-
}
55-
56-
pub type StepResult = Result<(), AppError>;
57-
58-
/// A trait to setup steps along the TUI app.
59-
pub trait Step {
60-
fn before_run(
61-
&mut self,
62-
_state: &mut AppData,
63-
_config: &mut SimpleCommitsConfig,
64-
) -> StepResult {
65-
Ok(())
66-
}
67-
68-
fn after_run(&mut self, _state: &mut AppData, _config: &mut SimpleCommitsConfig) -> StepResult {
69-
Ok(())
70-
}
71-
72-
fn run(&mut self, state: &mut AppData, config: &mut SimpleCommitsConfig) -> StepResult;
73-
}
74-
75-
#[macro_export]
76-
macro_rules! gen_steps {
77-
($($struct:ty),*) => {
78-
{
79-
let steps: Vec<Box<dyn super::Step>> = vec![
80-
$(
81-
Box::new(<$struct>::default()),
82-
)*
83-
];
84-
steps
85-
}
86-
};
87-
}
88-
27+
/// Builder for constructing a conventional commit message.
28+
///
29+
/// This builder follows the Conventional Commits specification and supports:
30+
/// - Commit type (feat, fix, docs, etc.)
31+
/// - Optional scope
32+
/// - Optional emoji
33+
/// - Title/subject line
34+
/// - Optional body/description
35+
/// - Optional footer notes
36+
/// - Breaking change markers and messages
8937
#[derive(Debug, Default, Clone)]
9038
pub struct CommitBuilder {
9139
r#type: Option<String>,
@@ -98,11 +46,15 @@ pub struct CommitBuilder {
9846
breaking_change_message: Option<String>, // This will filled if is_breaking_change is true
9947
}
10048

101-
pub struct Commit(String);
49+
/// The final commit message ready to be executed.
50+
pub struct Commit(pub String);
10251

52+
/// Errors that can occur when building a commit message.
10353
#[derive(Debug)]
10454
pub enum BuildError {
55+
/// The commit type (feat, fix, etc.) is required but was not provided.
10556
TypeRequired,
57+
/// The commit title/subject is required but was not provided.
10658
TitleRequired,
10759
}
10860

0 commit comments

Comments
 (0)