Skip to content
Open
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
38 changes: 23 additions & 15 deletions crates/pebble/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ impl<'a> TaskObject<'a> {
}
}

/// Emits a JSON-serializable payload to standard output.
///
/// Serializes the provided generic payload as a JSON string and writes it to `stdout`
/// followed by a newline. This centralizes the standard pattern of converting
/// structured data into machine-readable output for CLI commands.
///
/// # Arguments
///
/// * `payload` - A reference to any type that implements `serde::Serialize`.
///
/// # Errors
///
/// Returns an error if serialization fails (e.g., if the type contains a map with
/// non-string keys, though this is rare for standard API objects).
pub fn emit_json<T: Serialize>(payload: &T) -> Result<()> {
println!("{}", serde_json::to_string(payload)?);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better performance, consider serializing the JSON payload directly to stdout instead of creating an intermediate string. Using serde_json::to_writer with a locked stdout handle avoids the allocation of a potentially large string, which can be more efficient.

    let stdout = io::stdout();
    let mut handle = stdout.lock();
    serde_json::to_writer(&mut handle, payload)?;
    writeln!(handle)?;

Ok(())
}

/// Resolved runtime configuration and paths for command execution.
pub struct RunContext {
pub current_dir: PathBuf,
Expand Down Expand Up @@ -199,10 +218,7 @@ pub fn run_next(ctx: &RunContext, limit: usize) -> Result<()> {
.into_iter()
.map(|n| TaskObject::from_node(n, &graph, &ctx.tasks_dir))
.collect();
println!(
"{}",
serde_json::to_string(&serde_json::json!({ "tasks": objects }))?
);
emit_json(&serde_json::json!({ "tasks": objects }))?;
return Ok(());
}

Expand All @@ -227,18 +243,13 @@ pub fn run_show(ctx: &RunContext, id: &str, path_only: bool) -> Result<()> {
if path_only {
let rel_path = node.path.strip_prefix(&ctx.tasks_dir).unwrap_or(&node.path);
if ctx.json {
println!(
"{}",
serde_json::to_string(
&serde_json::json!({ "path": rel_path.display().to_string() })
)?
);
emit_json(&serde_json::json!({ "path": rel_path.display().to_string() }))?;
} else {
println!("{}", rel_path.display());
}
} else if ctx.json {
let obj = TaskObject::from_node(node, &graph, &ctx.tasks_dir);
println!("{}", serde_json::to_string(&obj)?);
emit_json(&obj)?;
} else {
let obj = TaskObject::from_node(node, &graph, &ctx.tasks_dir);
println!("Task: {} ({})", obj.title, obj.id);
Expand Down Expand Up @@ -266,10 +277,7 @@ pub fn run_config_get(ctx: &RunContext, key: &str) -> Result<()> {
})?;

if ctx.json {
println!(
"{}",
serde_json::to_string(&serde_json::json!({ "key": key, "value": value }))?
);
emit_json(&serde_json::json!({ "key": key, "value": value }))?;
} else {
println!("{value}");
}
Expand Down
7 changes: 2 additions & 5 deletions crates/pebble/src/commands/listing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use color_eyre::eyre::{Result, eyre};
use std::cmp::Ordering;
use std::collections::HashSet;

use super::{RunContext, TaskObject};
use super::{RunContext, TaskObject, emit_json};

/// Filters and switches accepted by `pebble list`.
pub struct ListOptions {
Expand Down Expand Up @@ -189,10 +189,7 @@ fn emit_task_list(ctx: &RunContext, graph: &TaskGraph, tasks: Vec<&TaskNode>) ->
.into_iter()
.map(|n| TaskObject::from_node(n, graph, &ctx.tasks_dir))
.collect();
println!(
"{}",
serde_json::to_string(&serde_json::json!({ "tasks": objects }))?
);
emit_json(&serde_json::json!({ "tasks": objects }))?;
} else {
for task in tasks {
println!(
Expand Down
6 changes: 4 additions & 2 deletions crates/pebble/src/commands_add.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::commands::{RunContext, TaskObject, read_stdin_if_dash, validate_task_references};
use crate::commands::{
RunContext, TaskObject, emit_json, read_stdin_if_dash, validate_task_references,
};
use crate::graph::TaskGraph;
use crate::models::{Priority, TaskFrontmatter, TaskNode, TaskStatus};
use crate::task_io::current_task_time;
Expand Down Expand Up @@ -193,7 +195,7 @@ pub fn run_add(ctx: &RunContext, input: RunAddInput) -> Result<()> {
let updated_graph = TaskGraph::new(graph.nodes);
let mut obj = TaskObject::from_node(&node, &updated_graph, &ctx.tasks_dir);
obj.path = display_path;
println!("{}", serde_json::to_string(&obj)?);
emit_json(&obj)?;
} else {
eprintln!("Created task {} at {}", new_id, display_path);
}
Expand Down
7 changes: 2 additions & 5 deletions crates/pebble/src/commands_archive.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::commands::RunContext;
use crate::commands::{RunContext, emit_json};
use crate::graph::TaskGraph;
use color_eyre::eyre::{Result, eyre};
use std::fs;
Expand Down Expand Up @@ -39,10 +39,7 @@ pub fn run_archive(ctx: &RunContext) -> Result<()> {
}

if ctx.json {
println!(
"{}",
serde_json::to_string(&serde_json::json!({ "archived": archived }))?
);
emit_json(&serde_json::json!({ "archived": archived }))?;
}

Ok(())
Expand Down
4 changes: 2 additions & 2 deletions crates/pebble/src/commands_diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::commands::RunContext;
use crate::commands::{RunContext, emit_json};
use crate::graph::TaskGraph;
use crate::models::TaskNode;
use color_eyre::eyre::Result;
Expand Down Expand Up @@ -53,7 +53,7 @@ fn report_diagnostics(ctx: &RunContext, errors: Vec<DiagnosticError>) -> Result<

if ctx.json {
let out = DiagnosticsOutput { ok, errors };
println!("{}", serde_json::to_string(&out)?);
emit_json(&out)?;
} else if ok {
println!("Graph is healthy. No issues found.");
} else {
Expand Down
9 changes: 2 additions & 7 deletions crates/pebble/src/commands_fix.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::commands::RunContext;
use crate::commands::{RunContext, emit_json};
use crate::commands_diagnostics::collect_diagnostics;
use crate::graph::TaskGraph;
use crate::models::TaskNode;
Expand Down Expand Up @@ -34,12 +34,7 @@ pub fn run_fix(ctx: &RunContext) -> Result<()> {
}

if ctx.json {
println!(
"{}",
serde_json::to_string(
&serde_json::json!({ "ok": ok, "fixed_tasks": modified_ids, "errors": errors })
)?
);
emit_json(&serde_json::json!({ "ok": ok, "fixed_tasks": modified_ids, "errors": errors }))?;
} else if modified_ids.is_empty() {
println!("No repairs needed.");
} else {
Expand Down
6 changes: 4 additions & 2 deletions crates/pebble/src/commands_write.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::commands::{RunContext, TaskObject, read_stdin_if_dash, validate_task_references};
use crate::commands::{
RunContext, TaskObject, emit_json, read_stdin_if_dash, validate_task_references,
};
use crate::config::{Config, validate_tasks_dir};
use crate::graph::TaskGraph;
use crate::models::{NotFoundError, Priority, TaskNode, TaskStatus, UsageError};
Expand Down Expand Up @@ -313,7 +315,7 @@ pub fn run_update(ctx: &RunContext, mut input: RunUpdateInput) -> Result<()> {
.insert(node.frontmatter.id.clone(), node.clone());
let updated_graph = TaskGraph::new(graph.nodes);
let obj = TaskObject::from_node(&node, &updated_graph, &ctx.tasks_dir);
println!("{}", serde_json::to_string(&obj)?);
emit_json(&obj)?;
} else {
eprintln!("Updated task {}", node.frontmatter.id);
}
Expand Down
3 changes: 2 additions & 1 deletion crates/pebble/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Parser;
use clap::error::ErrorKind;
use color_eyre::eyre::Result;
use pebble::cli::{Cli, Commands, ConfigCommands};
use pebble::commands::emit_json;
use pebble::commands::{
ListOptions, RunContext, run_config_get, run_list, run_next, run_search, run_show,
};
Expand All @@ -17,7 +18,7 @@ use std::path::{Path, PathBuf};
use std::process::ExitCode;

fn run_help_json() -> Result<()> {
println!("{}", serde_json::to_string(&help_json_schema())?);
emit_json(&help_json_schema())?;
Ok(())
}

Expand Down
26 changes: 26 additions & 0 deletions patch_commands.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<<<<<<< SEARCH
/// Resolved runtime configuration and paths for command execution.
pub struct RunContext {
=======
/// Emits a JSON-serializable payload to standard output.
///
/// Serializes the provided generic payload as a JSON string and writes it to `stdout`
/// followed by a newline. This centralizes the standard pattern of converting
/// structured data into machine-readable output for CLI commands.
///
/// # Arguments
///
/// * `payload` - A reference to any type that implements `serde::Serialize`.
///
/// # Errors
///
/// Returns an error if serialization fails (e.g., if the type contains a map with
/// non-string keys, though this is rare for standard API objects).
pub fn emit_json<T: Serialize>(payload: &T) -> Result<()> {
println!("{}", serde_json::to_string(payload)?);
Ok(())
}

/// Resolved runtime configuration and paths for command execution.
pub struct RunContext {
>>>>>>> REPLACE
Comment on lines +1 to +26

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This file appears to be a patch file, possibly an artifact from an automated tool, and contains what look like merge conflict markers. It should not be committed to the repository. Please remove this file from the pull request.

Loading