Compare commits

..

2 commits

Author SHA1 Message Date
aa1fbbc4b8 feat: add Windows cross-compilation and release artifacts
All checks were successful
Continuous Integration / Lint, Check & Test (push) Successful in 1m57s
Continuous Integration / Build Package (push) Successful in 2m2s
- Add mkWindowsCraneLib using x86_64-pc-windows-gnu target
- Add mkStreamdWindows using mingw-w64 toolchain for cross-compilation
- Export streamd-windows package from flake
- Add Windows build step and .exe artifact to release workflow
2026-04-13 19:38:15 +02:00
822d9194ae fix: cross-platform compatibility for Windows support
- Use directories::BaseDirs for config path fallback instead of hardcoded Unix path
- Default to notepad on Windows instead of vi for editor commands
- Skip +N line argument for notepad in todo edit (notepad doesn't support it)
2026-04-13 19:38:15 +02:00
7 changed files with 139 additions and 81 deletions

38
Cargo.lock generated
View file

@ -135,9 +135,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.60" version = "1.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@ -197,9 +197,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.6.2" version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" checksum = "406e68b4de5c59cfb8f750a7cbd4d31ae153788b8352167c1e5f4fc26e8c91e9"
dependencies = [ dependencies = [
"clap", "clap",
] ]
@ -432,9 +432,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.95" version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@ -448,15 +448,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.185" version = "0.2.184"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.16" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -787,7 +787,7 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]] [[package]]
name = "streamd" name = "streamd"
version = "0.2.3" version = "0.2.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz",
@ -1016,9 +1016,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.118" version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -1029,9 +1029,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.118" version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -1039,9 +1039,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.118" version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@ -1052,9 +1052,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.118" version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]

View file

@ -1,6 +1,6 @@
[package] [package]
name = "streamd" name = "streamd"
version = "0.2.3" version = "0.2.2"
edition = "2021" edition = "2021"
description = "Personal knowledge management and time-tracking CLI using @Tag annotations" description = "Personal knowledge management and time-tracking CLI using @Tag annotations"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"

View file

@ -4,13 +4,38 @@ use std::process::Command;
use chrono::{Days, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; use chrono::{Days, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use walkdir::WalkDir;
use crate::config::Settings; use crate::config::Settings;
use crate::error::StreamdError; use crate::error::StreamdError;
use crate::models::RepositoryConfiguration; use crate::extract::parse_markdown_file;
use crate::localize::localize_stream_file;
use crate::models::{LocalizedShard, RepositoryConfiguration};
use crate::timesheet::load_repository_config; use crate::timesheet::load_repository_config;
use super::load_markdown_shards; fn load_all_shards(base_folder: &Path, tz: Tz) -> Result<Vec<LocalizedShard>, StreamdError> {
let config = RepositoryConfiguration::new();
let mut shards = Vec::new();
for entry in WalkDir::new(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, &config, tz) {
shards.push(shard);
}
}
}
Ok(shards)
}
pub fn run(date: Option<String>) -> Result<(), StreamdError> { pub fn run(date: Option<String>) -> Result<(), StreamdError> {
let settings = Settings::load()?; let settings = Settings::load()?;
@ -44,7 +69,7 @@ pub fn run(date: Option<String>) -> Result<(), StreamdError> {
.unwrap() .unwrap()
.with_timezone(&Utc); .with_timezone(&Utc);
let all_shards = load_markdown_shards(base_folder, &RepositoryConfiguration::new(), tz)?; let all_shards = load_all_shards(base_folder, tz)?;
let mut daily_shards: Vec<_> = all_shards let mut daily_shards: Vec<_> = all_shards
.into_iter() .into_iter()
.filter(|s| { .filter(|s| {
@ -52,9 +77,8 @@ pub fn run(date: Option<String>) -> Result<(), StreamdError> {
.get("file_type") .get("file_type")
.map(|v| v == "daily") .map(|v| v == "daily")
.unwrap_or(false) .unwrap_or(false)
&& s.moment >= day_start
&& s.moment < day_end
}) })
.filter(|s| s.moment >= day_start && s.moment < day_end)
.collect(); .collect();
daily_shards.sort_by_key(|s| s.moment); daily_shards.sort_by_key(|s| s.moment);

View file

@ -1,19 +1,42 @@
use std::path::Path; use std::fs;
use std::process::Command; use std::process::Command;
use walkdir::WalkDir;
use crate::config::Settings; use crate::config::Settings;
use crate::error::StreamdError; use crate::error::StreamdError;
use crate::localize::TaskConfiguration; use crate::extract::parse_markdown_file;
use crate::localize::{localize_stream_file, TaskConfiguration};
use crate::models::LocalizedShard;
use super::load_markdown_shards; 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, chrono_tz::UTC)
{
shards.push(shard);
}
}
}
Ok(shards)
}
pub fn run(number: i32) -> Result<(), StreamdError> { pub fn run(number: i32) -> Result<(), StreamdError> {
let settings = Settings::load()?; let all_shards = all_files()?;
let all_shards = load_markdown_shards(
Path::new(&settings.base_folder),
&TaskConfiguration,
chrono_tz::UTC,
)?;
// Sort by moment (timestamp) // Sort by moment (timestamp)
let mut sorted_shards = all_shards; let mut sorted_shards = all_shards;

View file

@ -1,41 +1,6 @@
use std::fs;
use std::path::Path;
use chrono_tz::Tz;
use walkdir::WalkDir;
use crate::error::StreamdError;
use crate::extract::parse_markdown_file;
use crate::localize::localize_stream_file;
use crate::models::{LocalizedShard, RepositoryConfiguration};
pub mod completions; pub mod completions;
pub mod daily; pub mod daily;
pub mod edit; pub mod edit;
pub mod new; pub mod new;
pub mod timesheet; pub mod timesheet;
pub mod todo; pub mod todo;
pub fn load_markdown_shards(
base_folder: &Path,
config: &RepositoryConfiguration,
tz: Tz,
) -> Result<Vec<LocalizedShard>, StreamdError> {
let mut shards = Vec::new();
for entry in WalkDir::new(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, config, tz) {
shards.push(shard);
}
}
}
Ok(shards)
}

View file

@ -1,23 +1,49 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use std::path::Path; use std::path::Path;
use chrono::Datelike; use chrono::Datelike;
use chrono::NaiveDate; use chrono::NaiveDate;
use chrono::Utc; use chrono::Utc;
use chrono_tz::Tz; use chrono_tz::Tz;
use walkdir::WalkDir;
use crate::config::Settings; use crate::config::Settings;
const SEPARATOR_WIDTH: usize = 71; const SEPARATOR_WIDTH: usize = 71;
const COLUMN_SEPARATOR_WIDTH: usize = 65; const COLUMN_SEPARATOR_WIDTH: usize = 65;
use crate::error::StreamdError; use crate::error::StreamdError;
use crate::models::Timesheet; use crate::extract::parse_markdown_file;
use crate::localize::localize_stream_file;
use crate::models::{LocalizedShard, Timesheet};
use crate::timesheet::{ use crate::timesheet::{
extract_timesheets, generate_report, load_repository_config, BasicTimesheetConfiguration, extract_timesheets, generate_report, load_repository_config, BasicTimesheetConfiguration,
DayType, DayWarning, MonthReport, TimesheetReport, DayType, DayWarning, MonthReport, TimesheetReport,
}; };
use super::load_markdown_shards; fn load_all_shards(base_folder: &Path, tz: Tz) -> Result<Vec<LocalizedShard>, StreamdError> {
let mut shards = Vec::new();
for entry in WalkDir::new(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, tz)
{
shards.push(shard);
}
}
}
Ok(shards)
}
enum DisplayMode { enum DisplayMode {
Minutes, Minutes,
@ -324,7 +350,7 @@ pub fn run(decimal: bool, debug: bool) -> Result<(), StreamdError> {
let now = Utc::now(); let now = Utc::now();
// Load all markdown files and extract timesheets // Load all markdown files and extract timesheets
let all_shards = load_markdown_shards(base_folder, &BasicTimesheetConfiguration, tz)?; let all_shards = load_all_shards(base_folder, tz)?;
let timesheets = extract_timesheets(&all_shards, now, tz)?; let timesheets = extract_timesheets(&all_shards, now, tz)?;
// Generate the report // Generate the report

View file

@ -1,24 +1,44 @@
use std::fs; use std::fs;
use std::path::Path;
use std::process::Command; use std::process::Command;
use chrono::Utc; use chrono::Utc;
use walkdir::WalkDir;
use crate::config::Settings; use crate::config::Settings;
use crate::error::StreamdError; use crate::error::StreamdError;
use crate::localize::TaskConfiguration; use crate::extract::parse_markdown_file;
use crate::localize::{localize_stream_file, TaskConfiguration};
use crate::models::LocalizedShard; use crate::models::LocalizedShard;
use crate::query::find_shard_by_position; use crate::query::find_shard_by_position;
use super::load_markdown_shards; 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, chrono_tz::UTC)
{
shards.push(shard);
}
}
}
Ok(shards)
}
pub fn collect_open_tasks(show_future: bool) -> Result<Vec<LocalizedShard>, StreamdError> { pub fn collect_open_tasks(show_future: bool) -> Result<Vec<LocalizedShard>, StreamdError> {
let settings = Settings::load()?; let all_shards = all_files()?;
let all_shards = load_markdown_shards(
Path::new(&settings.base_folder),
&TaskConfiguration,
chrono_tz::UTC,
)?;
let now = Utc::now(); let now = Utc::now();
let mut tasks: Vec<LocalizedShard> = find_shard_by_position(&all_shards, "task", "open") let mut tasks: Vec<LocalizedShard> = find_shard_by_position(&all_shards, "task", "open")