Repository navigation
Closed
Labels
area:analyzerBinary analyzer functionalityBinary analyzer functionalitylang:rustRust implementationRust implementationneeds:testsNeeds test coverageNeeds test coveragepriority:highHigh priority taskHigh priority taskstatus:backlogTask in backlogTask in backlogstory-points: 55 story points5 story pointstype:enhancementNew feature or requestNew feature or request
Milestone
Description
Summary
Implement CLI arguments for output format selection, enabling users to control result limiting and choose between human-readable and JSONL output formats.
Context
The Stringy CLI currently accepts only an input file path. The output system needs to support multiple formats to serve different use cases:
- Human-readable: Interactive analysis with formatted tables
- JSONL: Machine-readable for automation and pipelines
- YARA: Security rule generation (future enhancement)
The core types (FoundString, Encoding, Tag, etc.) are fully defined in src/types.rs, and the output module skeleton exists at src/output/mod.rs.
Requirements
From the specification:
- 7.5: Implement
--top Nargument to limit results to top N strings by score - 7.6: Add
--jsonflag for JSONL output format (one JSON object per line) - 7.7: Default to human-readable table format when no format flag is specified
Proposed Solution
1. CLI Argument Structure
Extend the existing Cli struct in src/main.rs:
use clap::{Parser, ValueEnum};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "stringy")]
#[command(about = "Extract meaningful strings from binary files")]
#[command(version)]
struct Cli {
/// Input binary file to analyze
#[arg(value_name = "FILE")]
input: PathBuf,
/// Output format
#[arg(short = 'f', long, value_enum, default_value = "human")]
format: OutputFormat,
/// Shorthand for --format json
#[arg(long, conflicts_with = "format")]
json: bool,
/// Limit to top N results by score
#[arg(short = 't', long, value_name = "N")]
top: Option<usize>,
}
#[derive(Clone, Copy, ValueEnum)]
enum OutputFormat {
Human,
Json,
}2. Output Module Implementation
Create output formatters in src/output/:
src/output/mod.rs:
mod human;
mod json;
pub use human::HumanFormatter;
pub use json::JsonFormatter;
use crate::types::{FoundString, Result};
pub trait OutputFormatter {
fn format(&self, strings: &[FoundString]) -> Result<String>;
}src/output/human.rs:
use crate::output::OutputFormatter;
use crate::types::{FoundString, Result};
pub struct HumanFormatter;
impl OutputFormatter for HumanFormatter {
fn format(&self, strings: &[FoundString]) -> Result<String> {
let mut output = String::new();
output.push_str("Score Offset Section Tags String\n");
output.push_str("----- ------ ------- ---- ------\n");
for s in strings {
let tags_str = s.tags.iter()
.map(|t| format!("{:?}", t).to_lowercase())
.collect::<Vec<_>>()
.join(",");
output.push_str(&format!(
"{:5} 0x{:06x} {:12} {:14} {}\n",
s.score,
s.offset,
s.section.as_ref().unwrap_or(&String::from("<unknown>")),
tags_str,
s.text.chars().take(50).collect::<String>()
));
}
Ok(output)
}
}src/output/json.rs:
use crate::output::OutputFormatter;
use crate::types::{FoundString, Result};
use serde_json;
pub struct JsonFormatter;
impl OutputFormatter for JsonFormatter {
fn format(&self, strings: &[FoundString]) -> Result<String> {
let mut output = String::new();
for s in strings {
output.push_str(&serde_json::to_string(s)?);
output.push('\n');
}
Ok(output)
}
}3. Main Pipeline Integration
Update src/main.rs:
use stringy::output::{HumanFormatter, JsonFormatter, OutputFormatter};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
// Determine output format
let format = if cli.json {
OutputFormat::Json
} else {
cli.format
};
// TODO: Extract strings from binary (future PR)
// let strings = extract_strings(&cli.input)?;
// For now, use mock data
let mut strings = vec![]; // Will be populated by extraction pipeline
// Apply top limit if specified
if let Some(top) = cli.top {
strings.truncate(top);
}
// Format and output
let formatter: Box<dyn OutputFormatter> = match format {
OutputFormat::Human => Box::new(HumanFormatter),
OutputFormat::Json => Box::new(JsonFormatter),
};
let output = formatter.format(&strings)?;
println!("{}", output);
Ok(())
}4. Integration Tests
Create tests/cli_output_formats.rs:
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_default_human_readable_output() {
let mut cmd = Command::cargo_bin("stringy").unwrap();
cmd.arg("tests/fixtures/elf/simple_hello_world")
.assert()
.success()
.stdout(predicate::str::contains("Score"))
.stdout(predicate::str::contains("Offset"));
}
#[test]
fn test_json_flag() {
let mut cmd = Command::cargo_bin("stringy").unwrap();
cmd.arg("--json")
.arg("tests/fixtures/elf/simple_hello_world")
.assert()
.success()
.stdout(predicate::str::is_json());
}
#[test]
fn test_format_json() {
let mut cmd = Command::cargo_bin("stringy").unwrap();
cmd.arg("--format").arg("json")
.arg("tests/fixtures/elf/simple_hello_world")
.assert()
.success()
.stdout(predicate::str::is_json());
}
#[test]
fn test_top_limit() {
let mut cmd = Command::cargo_bin("stringy").unwrap();
let output = cmd.arg("--json")
.arg("--top").arg("5")
.arg("tests/fixtures/elf/simple_hello_world")
.output()
.unwrap();
let line_count = String::from_utf8_lossy(&output.stdout)
.lines()
.filter(|l| !l.is_empty())
.count();
assert!(line_count <= 5, "Expected at most 5 results, got {}", line_count);
}
#[test]
fn test_json_and_format_conflict() {
let mut cmd = Command::cargo_bin("stringy").unwrap();
cmd.arg("--json")
.arg("--format").arg("human")
.arg("tests/fixtures/elf/simple_hello_world")
.assert()
.failure(); // Should fail due to conflicting arguments
}Add test dependencies to Cargo.toml:
[dev-dependencies]
assert_cmd = "2.0"
predicates = "3.0"Implementation Checklist
- Update
Clistruct insrc/main.rswith new arguments - Implement
OutputFormattertrait insrc/output/mod.rs - Create
HumanFormatterinsrc/output/human.rs - Create
JsonFormatterinsrc/output/json.rs - Integrate formatters into main pipeline
- Handle
--toplimiting in main - Add
assert_cmdandpredicatesdev dependencies - Create
tests/cli_output_formats.rswith integration tests - Ensure all tests pass:
cargo test - Verify CLI works:
cargo run -- --help - Update documentation if needed
Testing Strategy
- Unit tests: Test formatters individually with mock
FoundStringdata - Integration tests: Test CLI argument parsing and output format selection
- Manual testing: Verify output appearance and usability
Dependencies
- Blocked by: Basic CLI Structure ✅ (Complete - basic clap structure exists)
- Blocks: Future output features (YARA format, filtering options)
Related Files
src/main.rs: CLI argument parsingsrc/output/mod.rs: Output module (currently empty)src/types.rs: Core types (FoundString,Encoding,Tag)tests/fixtures/: Test binary files for integration tests
References
- Documentation:
docs/src/output-formats.md - Documentation:
docs/src/cli.md - Requirements: 7.5, 7.6, 7.7
- Task ID: stringy-analyzer/output-format-cli-arguments
Metadata
Metadata
Assignees
Labels
area:analyzerBinary analyzer functionalityBinary analyzer functionalitylang:rustRust implementationRust implementationneeds:testsNeeds test coverageNeeds test coveragepriority:highHigh priority taskHigh priority taskstatus:backlogTask in backlogTask in backlogstory-points: 55 story points5 story pointstype:enhancementNew feature or requestNew feature or request
Activity
unclesp1d3r commentedon Nov 10, 2025
@coderabbitai Enhance this issue body with additional context and a proposed solution. Improve the issue title and assign the proper labels and other metadata.
[-]Output Format CLI Arguments[/-][+]CLI: Implement output format selection (--top, --json flags)[/+]traycerai commentedon Nov 10, 2025
Warning
Rate limit exceeded. Please try after 1229 seconds.
coderabbitai commentedon Nov 10, 2025
Tip
For best results, initiate chat on the files or code changes.
🧩 Analysis chain
🏁 Script executed:
Length of output: 1340
🏁 Script executed:
Length of output: 6456
🏁 Script executed:
Length of output: 9975
🏁 Script executed:
Length of output: 796
🏁 Script executed:
Length of output: 3066
🏁 Script executed:
Length of output: 5189
🏁 Script executed:
Length of output: 125950
🏁 Script executed:
Length of output: 61
🏁 Script executed:
Length of output: 8245
@unclesp1d3r I've successfully enhanced issue #31 with comprehensive details! Here's what was updated:
Changes Made
✅ Improved Title
Changed from "Output Format CLI Arguments" to "CLI: Implement output format selection (--top, --json flags)" - more descriptive and specific.
✅ Enhanced Issue Body
Added detailed sections including:
clapOutputFormattertrait designHumanFormatterandJsonFormatterimplementations✅ Added Metadata
priority:highlabel (appropriate for v0.1 milestone deliverable)area:analyzer,type:enhancement,status:backlog,lang:rust,needs:testsKey Technical Details in the Solution
The proposed implementation:
clapwithValueEnumfor type-safe format selection--jsonand--formatflagsassert_cmdandpredicatesFoundStringtypes fromsrc/types.rsThe issue is now ready for implementation with clear guidance on structure, testing, and acceptance criteria! 🚀
unclesp1d3r commentedon Jul 4, 2026
Implemented: --top and --json output-selection flags in src/main.rs. Closing as completed.