refactor: rewrite in rust
All checks were successful
Continuous Integration / Lint, Check & Test (push) Successful in 1m38s
Continuous Integration / Build Package (push) Successful in 1m54s

This commit is contained in:
Konstantin Fickel 2026-03-29 18:19:15 +02:00
parent 20a3e8b437
commit ed493cff29
Signed by: kfickel
GPG key ID: A793722F9933C1A5
72 changed files with 5684 additions and 3688 deletions

View file

@ -0,0 +1,11 @@
use clap::CommandFactory;
use clap_complete::{generate, Shell};
use std::io;
use crate::cli::Cli;
pub fn run(shell: Shell) {
let mut cmd = Cli::command();
let name = cmd.get_name().to_string();
generate(shell, &mut cmd, name, &mut io::stdout());
}

73
src/cli/commands/edit.rs Normal file
View file

@ -0,0 +1,73 @@
use std::fs;
use std::process::Command;
use walkdir::WalkDir;
use crate::config::Settings;
use crate::error::StreamdError;
use crate::extract::parse_markdown_file;
use crate::localize::{localize_stream_file, TaskConfiguration};
use crate::models::LocalizedShard;
fn all_files() -> Result<Vec<LocalizedShard>, StreamdError> {
let settings = Settings::load()?;
let mut shards = Vec::new();
for entry in WalkDir::new(&settings.base_folder)
.max_depth(1)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.extension().map(|e| e == "md").unwrap_or(false) {
let file_name = path.to_string_lossy().to_string();
let content = fs::read_to_string(path)?;
let stream_file = parse_markdown_file(&file_name, &content);
if let Ok(shard) = localize_stream_file(&stream_file, &TaskConfiguration) {
shards.push(shard);
}
}
}
Ok(shards)
}
pub fn run(number: i32) -> Result<(), StreamdError> {
let all_shards = all_files()?;
// Sort by moment (timestamp)
let mut sorted_shards = all_shards;
sorted_shards.sort_by_key(|s| s.moment);
if sorted_shards.is_empty() {
return Err(StreamdError::ConfigError("No files found".to_string()));
}
let selected_index = if number >= 0 {
// 0 = most recent, 1 = second most recent, etc.
let idx = sorted_shards.len() as i32 - number;
if idx < 0 {
return Err(StreamdError::ConfigError(
"Argument out of range".to_string(),
));
}
idx as usize
} else {
// -1 = oldest, -2 = second oldest, etc.
let idx = (-number - 1) as usize;
if idx >= sorted_shards.len() {
return Err(StreamdError::ConfigError(
"Argument out of range".to_string(),
));
}
idx
};
if let Some(file_path) = sorted_shards[selected_index].location.get("file") {
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
Command::new(&editor).arg(file_path).status()?;
}
Ok(())
}

5
src/cli/commands/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod completions;
pub mod edit;
pub mod new;
pub mod timesheet;
pub mod todo;

60
src/cli/commands/new.rs Normal file
View file

@ -0,0 +1,60 @@
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::Command;
use chrono::Local;
use crate::config::Settings;
use crate::error::StreamdError;
use crate::extract::parse_markdown_file;
pub fn run() -> Result<(), StreamdError> {
let settings = Settings::load()?;
let streamd_directory = &settings.base_folder;
let timestamp = Local::now().format("%Y%m%d-%H%M%S").to_string();
let preliminary_file_name = format!("{}_wip.md", timestamp);
let preliminary_path = Path::new(streamd_directory).join(&preliminary_file_name);
// Create initial file with heading
let content = "# ";
let mut file = fs::File::create(&preliminary_path)?;
file.write_all(content.as_bytes())?;
drop(file);
// Open in editor
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
let status = Command::new(&editor).arg(&preliminary_path).status()?;
if !status.success() {
return Err(StreamdError::IoError(std::io::Error::other(
"Editor exited with non-zero status",
)));
}
// Read the edited content
let edited_content = fs::read_to_string(&preliminary_path)?;
let parsed_content =
parse_markdown_file(preliminary_path.to_string_lossy().as_ref(), &edited_content);
// Determine final filename based on markers
let final_file_name = if let Some(ref shard) = parsed_content.shard {
if !shard.markers.is_empty() {
format!("{} {}.md", timestamp, shard.markers.join(" "))
} else {
format!("{}.md", timestamp)
}
} else {
format!("{}.md", timestamp)
};
let final_path = Path::new(streamd_directory).join(&final_file_name);
// Rename the file
fs::rename(&preliminary_path, &final_path)?;
println!("Saved as {}", final_file_name);
Ok(())
}

View file

@ -0,0 +1,52 @@
use std::fs;
use walkdir::WalkDir;
use crate::config::Settings;
use crate::error::StreamdError;
use crate::extract::parse_markdown_file;
use crate::localize::localize_stream_file;
use crate::models::LocalizedShard;
use crate::timesheet::{extract_timesheets, BasicTimesheetConfiguration};
fn all_files() -> Result<Vec<LocalizedShard>, StreamdError> {
let settings = Settings::load()?;
let mut shards = Vec::new();
for entry in WalkDir::new(&settings.base_folder)
.max_depth(1)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.extension().map(|e| e == "md").unwrap_or(false) {
let file_name = path.to_string_lossy().to_string();
let content = fs::read_to_string(path)?;
let stream_file = parse_markdown_file(&file_name, &content);
if let Ok(shard) = localize_stream_file(&stream_file, &BasicTimesheetConfiguration) {
shards.push(shard);
}
}
}
Ok(shards)
}
pub fn run() -> Result<(), StreamdError> {
let all_shards = all_files()?;
let mut sheets = extract_timesheets(&all_shards)?;
sheets.sort_by_key(|s| s.date);
for sheet in sheets {
println!("{}", sheet.date);
let times: Vec<String> = sheet
.timecards
.iter()
.map(|card| format!("{},{}", card.from_time, card.to_time))
.collect();
println!("{}", times.join(","));
}
Ok(())
}

56
src/cli/commands/todo.rs Normal file
View file

@ -0,0 +1,56 @@
use std::fs;
use walkdir::WalkDir;
use crate::config::Settings;
use crate::error::StreamdError;
use crate::extract::parse_markdown_file;
use crate::localize::{localize_stream_file, TaskConfiguration};
use crate::models::LocalizedShard;
use crate::query::find_shard_by_position;
fn all_files() -> Result<Vec<LocalizedShard>, StreamdError> {
let settings = Settings::load()?;
let mut shards = Vec::new();
for entry in WalkDir::new(&settings.base_folder)
.max_depth(1)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.extension().map(|e| e == "md").unwrap_or(false) {
let file_name = path.to_string_lossy().to_string();
let content = fs::read_to_string(path)?;
let stream_file = parse_markdown_file(&file_name, &content);
if let Ok(shard) = localize_stream_file(&stream_file, &TaskConfiguration) {
shards.push(shard);
}
}
}
Ok(shards)
}
pub fn run() -> Result<(), StreamdError> {
let all_shards = all_files()?;
for task_shard in find_shard_by_position(&all_shards, "task", "open") {
if let Some(file_path) = task_shard.location.get("file") {
let content = fs::read_to_string(file_path)?;
let lines: Vec<&str> = content.lines().collect();
let start = task_shard.start_line.saturating_sub(1);
let end = std::cmp::min(task_shard.end_line, lines.len());
println!("--- {}:{} ---", file_path, task_shard.start_line);
for line in &lines[start..end] {
println!("{}", line);
}
println!();
}
}
Ok(())
}