From 0b316ad54fec79a65cc69ad41be50bd73fc1f330 Mon Sep 17 00:00:00 2001 From: Sebastian Moser Date: Wed, 14 Oct 2026 01:50:08 +0200 Subject: [PATCH] updated AGENTS.md and pps site works again. --- AGENTS.md | 17 +- Cargo.lock | 80 +++++ Cargo.toml | 1 + apps/os/src/main.rs | 2 +- docs/part-system.md | 625 +++++++++++++++++++++++++++++++++ flake.nix | 2 +- macros/src/lib.rs | 1 + marts/Cargo.toml | 1 + marts/spacetime/src/lib.rs | 13 +- marts/src/auth/client.rs | 2 +- marts/src/c2vi/part.rs | 68 ++-- marts/src/cli/mod.rs | 6 +- marts/src/dev/mod.rs | 2 +- marts/src/lib.rs | 1 - marts/src/ppc/main_page.rs | 194 ---------- marts/src/ppc/mod.rs | 192 +++++++++- marts/src/ppc/server.rs | 15 +- marts/src/sci/inbox/mod.rs | 8 + marts/src/sci/mod.rs | 11 +- marts/src/sci/timetrack/mod.rs | 76 +++- 20 files changed, 1053 insertions(+), 264 deletions(-) create mode 100644 docs/part-system.md delete mode 100644 marts/src/ppc/main_page.rs create mode 100644 marts/src/sci/inbox/mod.rs diff --git a/AGENTS.md b/AGENTS.md index 280be8f..ee51b72 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,21 +1,26 @@ # Mize -Mize is a strongly typed "filesystem" for the age of connectivity, elevating the Unix file philosophy into modern distributed computing. +Mize is many things. +- a "strongly typed filesystem for the age of everyone having many devices" +- a stdlib replacement +- a framework to make malleable/extensible/configurable/debugable software fast +- a OS userspace (mainly linux but later also any other kernels) +- a library for COP = Configuration Oriented Programming +- a thing providing one single API through which any data can be interacted with ## Codebase Structure ``` packages/ -├── mize/ # Core Mize framework (Rust) +├── src/ # Core Mize framework (Rust) ├── marts/ # Mize parts collection (Rust/TypeScript) -├── mme/ # Mize Module Environment (Rust) -├── ppc/ # Platform-specific components -├── vic/ # Victor CLI tool -└── ac_mize_macros/# Mize macros +└── macros/ # Mize macros ``` ## Key Components +All code written for, with or as part as mize (except for the small core) is written as a Mize Part. see `docs/part-system.md` for how that works. + To understand the main components of the architecture you can refer to `docs/architecture.md`. ## Require clarification and plan approval before making code changes diff --git a/Cargo.lock b/Cargo.lock index 5d3e67d..83589ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,6 +1053,30 @@ dependencies = [ "cc", ] +[[package]] +name = "cmd_lib" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc93f60133215cad2ef47e47db5a386c78874a2630c2c2be68f0f543b3ce0c2c" +dependencies = [ + "cmd_lib_macros", + "env_logger", + "faccess", + "log", + "os_pipe", +] + +[[package]] +name = "cmd_lib_macros" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd39b5c2657c1a0064aa5d73636dac4a217edeeb6c5f8a5f52c123a758462ece" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "cocoa" version = "0.26.1" @@ -2407,6 +2431,29 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equator" version = "0.4.2" @@ -2534,6 +2581,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "faccess" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" +dependencies = [ + "bitflags 1.3.2", + "libc", + "winapi", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -4616,6 +4674,7 @@ dependencies = [ "chrono", "chrono-tz", "clap", + "cmd_lib", "dioxus", "dioxus-ssr", "flume 0.12.0", @@ -5167,6 +5226,17 @@ dependencies = [ "mize", ] +[[package]] +name = "mize-spacetime" +version = "0.1.0" +dependencies = [ + "chrono", + "chrono-tz", + "log", + "spacetimedb", + "url", +] + [[package]] name = "mize_macros" version = "0.1.0" @@ -5715,6 +5785,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "p256" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index bd0c346..fadb769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "macros", "apps/os", "apps/obsidian-plugin", + "marts/spacetime", ] default-members = [ "apps/obsidian-plugin", diff --git a/apps/os/src/main.rs b/apps/os/src/main.rs index e154abc..a59e95b 100644 --- a/apps/os/src/main.rs +++ b/apps/os/src/main.rs @@ -69,7 +69,7 @@ fn tried_main() -> MizeResult<()> { //"matrix": marts::part_matrix, "ppc.server": marts::ppc::server::part_ppc_server, "c2vi": marts::part_c2vi, - "sci.timetrack": marts::timetrack::part_timetrack, + "sci.timetrack": marts::sci::timetrack::part_timetrack, }; diff --git a/docs/part-system.md b/docs/part-system.md new file mode 100644 index 0000000..68131a1 --- /dev/null +++ b/docs/part-system.md @@ -0,0 +1,625 @@ +# Mize Part System (LLM generated guide) + +## Overview + +Mize is built around a **parts architecture** that allows you to compose functionality modularly. Each part is a self-contained unit that can: + +- Store and manage its own state +- Expose methods to other parts +- Register itself with the Mize instance +- Define configuration options + +Parts are the primary way to extend Mize's functionality. The system is designed so that parts can depend on each other, and users can enable/disable parts as needed. + +--- + +## Core Concepts + +### The MizePart Trait + +All parts implement the `MizePart` trait, which provides: + +```rust +pub trait MizePart: MizePartGenerated { + fn name(&self) -> &'static str; // Part identifier + fn get_mize(&mut self) -> &mut Mize; // Access to the Mize instance + fn init(&mut self, mize: &mut Mize) -> MizeResult<()>; // Initialization + fn run(&mut self, mize: &mut Mize) -> MizeResult<()>; // Execution + fn deps(&self) -> &'static [&'static str]; // Dependencies + fn opts(&self, mize: &mut Mize); // Config options + // ... async variants +} +``` + +### Part Registration + +Parts register themselves with the Mize instance using: + +- **`mize.register_part(Box::new(MyPart { ... }))`** - For parts with state and methods +- **`mize.add_name_only_part("name")`** - For simple parts that only need to be known by name (and have no state) + +### Part Access + +Parts can access other parts using: + +```rust +// Get a part by name (returns a guard that ensures the part is returned) +let mut part = mize.get_part("part_name")?; + +// Get a typed part (for parts with structs) +let mut typed_part = mize.get_part_native::("other_part")?; +``` + +**Important**: Users must ensure that dependent parts are enabled before the parts that depend on them. There's no automatic dependency resolution. + +--- + +## Current Implementation (Compiled-in Parts) + +Currently, all parts are compiled into the `marts` crate and registered via the `compile_in_parts!` macro in the application's `main.rs`. + +### Example: `apps/os/src/main.rs` + +```rust +compile_in_parts! { + mize, + "cli": marts::cli, + "auth": marts::part_client_auth, + "spacetime": marts::spacetime, + "ppc.server": marts::ppc::server::part_ppc_server, + "c2vi": marts::part_c2vi, + "sci": marts::sci::part_sci, + "sci.timetrack": marts::sci::timetrack::part_timetrack, + "sci.inbox": marts::sci::inbox::part_sci_inbox, +}; +``` + +Each entry maps a part name to a function that registers the part with the Mize instance. + +### How It Works + +1. The macro reads the `enable` config option (a list of part names) +2. For each enabled part, it calls the corresponding registration function +3. Each registration function creates the part and calls `mize.register_part()` + +--- + +## Creating a New Part + +### Option 1: Simple Name-Only Part + +For parts that don't need state or methods: + +```rust +// In your part module +pub fn part_my_simple_part(mize: &mut Mize) -> MizeResult<()> { + // Just register the name + mize.add_name_only_part("my_simple_part"); + Ok(()) +} +``` + +### Option 2: Part with State and Methods (Using `mize_part` Macro) + +For parts that need to store state and expose methods: + +```rust +use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult}; + +#[mize_part] // or #[mize_part("custom_name")] for custom name +#[derive(Default)] +pub struct MyPart { + mize: Mize, // Required field + counter: u32, // Your custom state + api_key: String, // More state +} + +// Implement MizePartCreate for parts that need custom initialization +impl MizePartCreate for MyPart { + // Optional: override create_generated for custom initialization +} + +impl MizePart for MyPart { + // Optional: override name if not using the struct name + fn name(&self) -> &'static str { + "my_part" // Default would be "mypart" from struct name + } + + // Optional: define config options this part needs + fn opts(&self, mize: &mut Mize) { + mize.new_opt("my_part.api_key"); + mize.new_opt("my_part.counter_initial"); + } + + // Optional: initialization logic + fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { + self.counter = mize.get_config("my_part.counter_initial")?.value_u32()?; + self.api_key = mize.get_config("my_part.api_key")?.value_string()?; + Ok(()) + } + + // Optional: define dependencies on other parts + fn deps(&self) -> &'static [&'static str] { + &["auth"] // This part requires "auth" to be loaded first + } +} + +// The registration function +pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> { + mize.register_part(Box::new(MyPart { + mize: mize.clone(), + counter: 0, + api_key: String::new(), + }))?; + Ok(()) +} + +// You can add methods to your part struct +impl MyPart { + pub fn increment(&mut self) -> u32 { + self.counter += 1; + self.counter + } + + pub fn get_api_key(&self) -> &str { + &self.api_key + } +} +``` + +### Using the Part from Another Part + +```rust +pub fn part_another_part(mize: &mut Mize) -> MizeResult<()> { + // Get the MyPart instance + let mut my_part = mize.get_part_native::("my_part")?; + + // Call its methods + let count = my_part.increment(); + let api_key = my_part.get_api_key(); + + // ... your logic + + Ok(()) +} +``` + +--- + +## The `mize_part` Macro + +The `#[mize_part]` procedural macro simplifies part creation by automatically implementing the required traits. + +### What It Generates + +For a struct like: + +```rust +#[mize_part] +#[derive(Default)] +pub struct MyPart { + mize: Mize, + // ... your fields +} +``` + +The macro generates: + +1. **`MizePartGenerated` implementation** - Provides default implementations for the generated trait methods +2. **`MizePartCreateGenerated` implementation** - Provides a default `create_generated` method +3. **`MizePartCreateThisSucks` implementation** - Compatibility trait +4. **Extern "C" function** - Named `make_mize_part_` for dynamic loading + +### Custom Part Name + +```rust +#[mize_part("custom_name")] +pub struct MyPart { + mize: Mize, +} +``` + +This will use "custom_name" instead of the derived "mypart". + +### Requirements + +- The struct **must** have a field named `mize: Mize` +- The struct should derive `Default` or implement it + +--- + +## Configuration + +### Defining Config Options +(about to be deprecad, does nothing) + +Parts declare what config options they need in their `opts()` method: + +```rust +impl MizePart for MyPart { + fn opts(&self, mize: &mut Mize) { + mize.new_opt("my_part.api_url"); + mize.new_opt("my_part.timeout"); + } +} +``` + +### Using Config Options + +Parts retrieve config using `mize.get_config()`: + +```rust +fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { + let api_url = mize.get_config("my_part.api_url")?.value_string()?; + let timeout = mize.get_config("my_part.timeout")?.value_u64()?; + Ok(()) +} +``` + +### User Configuration + +Users configure parts via TOML files or environment variables. Example `config.toml`: + +```toml +[my_part] +api_url = "https://api.example.com" +timeout = 30 + +[enable] +# List of parts to enable +my_part = true +auth = true +spacetime = true +``` + +Or via command line: + +```bash +mize --opt my_part.api_url=https://api.example.com run +``` + +--- + +## Part Dependencies + +### Declaring Dependencies +(about to be deprecad, does nothing) + +Parts declare their dependencies in the `deps()` method: + +```rust +impl MizePart for MyPart { + fn deps(&self) -> &'static [&'static str] { + &["auth", "spacetime"] + } +} +``` + +**Note**: This is currently informational only. Users must manually ensure dependencies are enabled. + +### Accessing Dependencies + +Use `get_part_native()` to access other parts: + +```rust +fn run(&mut self, mize: &mut Mize) -> MizeResult<()> { + // Get the auth part + let mut auth = mize.get_part_native::("client_auth")?; + + // Use it + let token = auth.id_token()?; + + // Get spacetime + let mut spacetime = mize.get_part_native::("spacetime")?; + + Ok(()) +} +``` + +--- + +## Future Vision: Separate Crates + +The goal is to move each part to its own crate following the naming convention **`mize-part-`**. when creating a part make a new Cargo project like this already. + +### Structure of a Part Crate + +``` +mize-part-discord/ +├── Cargo.toml +└── src/ + └── lib.rs +``` + +### Example: `mize-part-discord/Cargo.toml` + +```toml +[package] +name = "mize-part-discord" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +mize = { path = "../mize" } +serenity = "0.12" +``` + +### Example: `mize-part-discord/src/lib.rs` + +```rust +use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult}; +use serenity::Client; + +#[mize_part] +#[derive(Default)] +pub struct DiscordPart { + mize: Mize, + client: Option, +} + +impl MizePartCreate for DiscordPart {} + +impl MizePart for DiscordPart { + fn name(&self) -> &'static str { + "discord" + } + + fn opts(&self, mize: &mut Mize) { + mize.new_opt("discord.token"); + } + + fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { + let token = mize.get_config("discord.token")?.value_string()?; + // Initialize serenity client... + Ok(()) + } +} + +// The registration function - THIS IS WHAT MATTERS +pub fn part_discord(mize: &mut Mize) -> MizeResult<()> { + mize.register_part(Box::new(DiscordPart { + mize: mize.clone(), + client: None, + }))?; + Ok(()) +} +``` + +### Using Separate Part Crates + +In your application's `Cargo.toml`: + +```toml +[dependencies] +mize = { path = "../mize" } +mize-part-discord = { path = "../mize-part-discord" } +mize-part-auth = { path = "../mize-part-auth" } +``` + +In your application's `main.rs`: + +```rust +compile_in_parts! { + mize, + "discord": mize_part_discord::part_discord, + "auth": mize_part_auth::part_auth, +}; +``` + +## Complete Example: Creating a New Part + +Let's create a complete example of a new part called `mize-part-greeting`. + +### Step 1: Create the Crate + +```bash +cargo new --lib mize-part-greeting +cd mize-part-greeting +``` + +### Step 2: Edit `Cargo.toml` + +```toml +[package] +name = "mize-part-greeting" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +mize = { path = "../mize" } +``` + +### Step 3: Create the Part (`src/lib.rs`) + +```rust +use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult}; + +/// A simple greeting part that stores a name and can generate greetings +#[mize_part] +#[derive(Default)] +pub struct GreetingPart { + mize: Mize, + name: String, +} + +impl MizePartCreate for GreetingPart {} + +impl MizePart for GreetingPart { + fn name(&self) -> &'static str { + "greeting" + } + + fn opts(&self, mize: &mut Mize) { + mize.new_opt("greeting.name"); + mize.new_opt("greeting.formal"); + } + + fn init(&mut self, mize: &mut Mize) -> MizeResult<()> { + self.name = mize + .get_config("greeting.name")? + .value_string() + .unwrap_or_else(|_| "World".to_string()); + Ok(()) + } +} + +impl GreetingPart { + /// Generate a greeting message + pub fn greet(&self) -> String { + let formal = self + .mize + .get_config("greeting.formal") + .map(|v| v.value_bool().unwrap_or(false)) + .unwrap_or(false); + + if formal { + format!("Good day, {}!", self.name) + } else { + format!("Hello, {}!", self.name) + } + } + + /// Set the name + pub fn set_name(&mut self, name: String) { + self.name = name; + } + + /// Get the current name + pub fn get_name(&self) -> &str { + &self.name + } +} + +/// Registration function - this is what the compile_in_parts! macro will call +pub fn part_greeting(mize: &mut Mize) -> MizeResult<()> { + mize.register_part(Box::new(GreetingPart { + mize: mize.clone(), + name: String::new(), + }))?; + Ok(()) +} +``` + +### Step 4: Add to Your Application + +In your application's `Cargo.toml`: + +```toml +[dependencies] +mize = { path = "../mize" } +mize-part-greeting = { path = "../mize-part-greeting" } +``` + +In your application's `main.rs`: + +```rust +compile_in_parts! { + mize, + "greeting": mize_part_greeting::part_greeting, +}; +``` + +### Step 5: Use the Part + +```rust +// In another part or in your CLI +let mut greeting = mize.get_part_native::("greeting")?; +println!("{}", greeting.greet()); + +// Change the name +greeting.set_name("Alice".to_string()); +println!("{}", greeting.greet()); +``` + +### Step 6: Configure It + +Create a `config.toml`: + +```toml +[greeting] +name = "Bob" +formal = true + +[enable] +greeting = true +``` + +Run with: + +```bash +mize --config config.toml run +``` + +--- + +## Best Practices + +1. **Naming**: Use `mize-part-` for crate names and `part_` for registration functions +2. **State**: Store part-specific state in the part struct +3. **Dependencies**: Declare dependencies in `deps()` and document them +4. **Config**: Declare all config options in `opts()` +5. **Error Handling**: Use `MizeResult` and `mize_err!` for errors +6. **Documentation**: Document your part's purpose, config options, and methods + +--- + +## Summary + +- Parts are modular units of functionality in Mize +- Each part implements the `MizePart` trait +- Use `#[mize_part]` macro for parts with state and methods +- Register parts with `mize.register_part()` or `mize.add_name_only_part()` +- Parts access config via `mize.get_config()` +- Parts access other parts via `mize.get_part_native::()` +- Users enable parts via the `enable` config option +- Future: Each part will be its own crate named `mize-part-` + +--- + +## Quick Reference + +### Common Patterns + +**Register a part:** +```rust +mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?; +``` + +**Get a part:** +```rust +let mut part = mize.get_part_native::("my_part")?; +``` + +**Get config:** +```rust +let value = mize.get_config("my_part.setting")?.value_string()?; +``` + +**Declare config option:** +```rust +mize.new_opt("my_part.setting"); +``` + +**Define a part struct:** +```rust +#[mize_part] +#[derive(Default)] +pub struct MyPart { + mize: Mize, + // your fields... +} +``` + +**Registration function:** +```rust +pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> { + mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?; + Ok(()) +} +``` diff --git a/flake.nix b/flake.nix index 872d4ed..c6caa17 100644 --- a/flake.nix +++ b/flake.nix @@ -382,7 +382,7 @@ in { serviceConfig = { Restart = "on-failure"; ExecStart = if config.mize.debugBinary == null then "${self.packages.${system}.mize}/bin/mize daemon" else "${config.mize.debugBinary} daemon"; - After = [ "graphical-session.target" ]; + #After = [ "graphical-session.target" ]; PartOf = [ "graphical-session.target" ]; # this should mean DISPLAY and WAYLAND_DISPLAY are set in the service env... }; }; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 68bed34..0dc0200 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -129,6 +129,7 @@ enum Literal { Int(i128), Float(f64), Bool(bool), + #[allow(unused)] Null, } diff --git a/marts/Cargo.toml b/marts/Cargo.toml index 386b871..05201cf 100644 --- a/marts/Cargo.toml +++ b/marts/Cargo.toml @@ -47,6 +47,7 @@ axum-oidc = { version = "=1.0.0-dev-1", optional = true } tower = { version = "0.5.3", optional = true } tower-sessions = { version = "0.15.0", optional = true } tower-http = { version = "0.7.1", optional = true, features = [ "cors", "trace" ] } +cmd_lib = "2.0.1" diff --git a/marts/spacetime/src/lib.rs b/marts/spacetime/src/lib.rs index e520847..2f128f1 100644 --- a/marts/spacetime/src/lib.rs +++ b/marts/spacetime/src/lib.rs @@ -291,8 +291,7 @@ fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String) info!("end_of_day: {}", end_timestamp); let mut export = String::new(); - export.push_str(&format!("# export for {}\n\n", date)); - export.push_str(&format!("## actions\n")); + //export.push_str(&format!("### actions\n")); let profile = ctx.with_tx(|tx| tx.db.profiles().username().find(&username)).ok_or(format!("Profile with username {} not found", username))?; @@ -306,17 +305,19 @@ fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String) .to_chrono_date_time() .map_err(|err| err.to_string())? .with_timezone(&Europe::Vienna) - .format("%H:%M:%S"); + .format("%H:%M"); let end = event.end .to_chrono_date_time() .map_err(|err| err.to_string())? .with_timezone(&Europe::Vienna) - .format("%H:%M:%S"); + .format("%H:%M"); let line = format!("{} to {} ... {}\n", start, end, event.action.name); export.push_str(line.as_str()); } + return Ok(export); // don't do thoughts right now + export.push_str("\n"); export.push_str("## thoughts\n"); let thoughts = ctx.with_tx(|tx| { @@ -355,8 +356,8 @@ pub fn migrate(ctx: &mut ProcedureContext) -> String { #[procedure] pub fn test(ctx: &mut ProcedureContext) -> String { let mut s = String::from("hoooooooooooooooooooooo\n"); - let a: String = ctx.with_tx(|tx| { - let mut s = String::new(); + let a: String = ctx.with_tx(|_tx| { + let s = String::new(); diff --git a/marts/src/auth/client.rs b/marts/src/auth/client.rs index 490ccdf..c85f810 100644 --- a/marts/src/auth/client.rs +++ b/marts/src/auth/client.rs @@ -265,7 +265,7 @@ pub fn part_client_auth(mize: &mut Mize) -> MizeResult<()> { mize: mize.clone(), }; - let mut secrets = match load_secrets(mize) { + let secrets = match load_secrets(mize) { Ok(secrets) => Some(secrets), Err(err) => { tracing::warn!("err reading secrets: {:?}", err); diff --git a/marts/src/c2vi/part.rs b/marts/src/c2vi/part.rs index 9758ac8..8729421 100644 --- a/marts/src/c2vi/part.rs +++ b/marts/src/c2vi/part.rs @@ -6,6 +6,8 @@ use clap::{Arg, Command}; use mize::item::ItemData; use rusqlite::{params, Connection}; use serde_json::{json, Value}; +use clap::ArgMatches; +use cmd_lib::run_cmd as sh; use dioxus::prelude::*; use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult}; @@ -18,13 +20,13 @@ use generated::spacetime::{ Action, ActivitiesTableAccess, Activity, DbConnection, LogEventsTableAccess, TaskPart, TaskPartTableAccess, activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, update_activity, update_upcoming }; use spacetimedb_sdk::{ - credentials, DbContext, Error, Event, Identity, Status, SubscriptionHandle, Table, + credentials, DbContext, Error, Event, Identity, Status, TableWithPrimaryKey, }; static MIZE_SRC: &str = "/home/me/work/config/mize"; static SRC: &str = "/home/me/work/config"; -static RSYNC_EXCLUDES: &str = "--exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg"; +static RSYNC_EXCLUDES: &str = "--exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg --exclude /src --exclude obsidian-brat --exclude forked-zed"; #[mize_part] #[derive(Default)] @@ -78,7 +80,7 @@ pub fn part_c2vi(mize: &mut Mize) -> MizeResult<()> { name: "SC2 Publish Daily Journal".to_string(), callback: Box::new(|mut mize| { let matrix = mize.get_part_native::("matrix")?; - matrix.send_message(room_id); + //matrix.send_message(room_id); Ok(()) }), @@ -92,9 +94,11 @@ fn c2vi_deploy_things(mize: &mut Mize) -> MizeResult<()> { let mut cli = mize.get_part_native::("cli")?; cli.set_command("dev"); + let bin_arg = Arg::new("bin").short('b'); + cli.subcommand( Command::new("deploy-lush").alias("dlu").arg(&bin_arg), - Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "lush")), + Box::new(move |matches: &ArgMatches, mize| remote_deploy(matches, mize, "te")), )?; cli.subcommand( Command::new("deploy-mac").alias("dmc").arg(&bin_arg), @@ -124,10 +128,6 @@ fn c2vi_deploy_things(mize: &mut Mize) -> MizeResult<()> { Command::new("deploy-spacetime").alias("dsp").arg(&bin_arg), Box::new(deploy_spacetime), )?; - cli.subcommand( - Command::new("generate").alias("gen").arg(&bin_arg), - Box::new(md_generate), - )?; Ok(()) } @@ -168,33 +168,61 @@ pub fn use_spacetime(query: T) { //pub fn use_by_pk(identity: Identity) //pub fn use_one + pub fn c2vi_dash_main() -> Element { let mut current = use_signal_sync(|| "".to_string()); + let mut next = use_signal_sync(|| "".to_string()); //TaskPart:: //use_one() use_effect(move || { let mut mize = use_context::(); - let mut spacetime = mize.get_part_native::("spacetime").unwrap(); + let spacetime = match mize.get_part_native::("spacetime") { + Ok(val) => val, + Err(e) => { + e.log(); + return; + }, + }; let con = spacetime.con(); con.subscription_builder() .on_applied(move |ctx| { - let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap(); + let data = match ctx.db.task_part().identity().find(&ctx.identity()) { + Some(val) => val, + None => { + debug!("can't find task part for user"); + return; + }, + }; current.set(data.current.name.clone()); + if let Some(first_upcoming) = data.upcoming.first() { + next.set(first_upcoming.name.clone()); + } else { + next.set("".to_string()); + } }) .add_query(|q| q.from.task_part().r#where(|t| t.identity.eq(con.identity()))) .subscribe(); con.db .task_part() - .on_update(move |_, old: &TaskPart, new: &TaskPart| { + .on_update(move |_, _old: &TaskPart, new: &TaskPart| { current.set(new.current.name.clone()); + if let Some(first_upcoming) = new.upcoming.first() { + next.set(first_upcoming.name.clone()); + } else { + next.set("".to_string()); + } }); }); rsx! { div { - style: "text-align: center; padding: 2rem; font-size: 50px;", + style: "text-align: center; padding: 2rem; font-size: 120px;", p { "{current}" } + p { + style: "font-size: 50px; color: grey; opacity: 0.7;", + "{next}" + } } } } @@ -774,8 +802,7 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul let bin = match matches.get_one::("bin") { Some(val) => val.to_owned(), None => { - do_mize_restart = true; - format!("{}-os", hostname) + "mize".to_owned() } }; @@ -785,7 +812,7 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul "files" => "/home/files/here/config", "nico" => "/root/work/config-c2vi", "ppc-hosting" => "/root/host/c2vi-config", - _ => "main-idk", + _ => "/home/me/here/config", }; let mize_store_dir = match hostname { "lush" => "/home/me/host/mize", @@ -793,13 +820,15 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul "files" => "/home/files/storage/files/mize", "nico" => "/root/work/app-data/mize", "ppc-hosting" => "/root/host/mize", - _ => "/tmp/idk", + _ => "/home/me/host/mize", }; let rclone_flag = match hostname { "nico" => "--chown=root:root", + "ppc-hosting" => "--chown=root:root", _ => "", }; + sh!(echo cmd: rsync -r -u -v -a --delete --perms $rclone_flag --include=".git/" --include=".git/**" --exclude="target" --mkpath $RSYNC_EXCLUDES $SRC/ $hostname:$remote_src_dir)?; sh!(rsync -r -u -v -a --delete --perms $rclone_flag --include=".git/" --include=".git/**" --exclude="target" --mkpath $RSYNC_EXCLUDES $SRC/ $hostname:$remote_src_dir)?; let ssh_cmd = format!( @@ -812,7 +841,7 @@ pub fn remote_deploy(matches: &ArgMatches, _: Mize, hostname: &str) -> MizeResul fi tmux send-keys -t mize-deploy "DO_MIZE_RESTART={do_mize_restart}" Enter - tmux send-keys -t mize-deploy "cargo build --manifest-path {remote_src_dir}/Cargo.toml --bin {bin}" Enter + tmux send-keys -t mize-deploy "cargo build --manifest-path {remote_src_dir}/mize/Cargo.toml --bin {bin}" Enter tmux send-keys -t mize-deploy 'BUILD_SUCCESS=$?' Enter tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > {mize_store_dir}/daemon.env' Enter tmux send-keys -t mize-deploy '[[ "$BUILD_SUCCESS" == "0" ]] && echo "MIZE_CONFIG_FILES={mize_store_dir}/config.toml" >> {mize_store_dir}/daemon.env' Enter @@ -888,7 +917,7 @@ pub fn main_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> { ); std::env::set_var("SRC", SRC); } - let cmd = str!( + let cmd = r#" if ! tmux has-session -t mize-deploy 2>/dev/null; then systemd-run --user --scope --description="mize deploy tmux session" tmux new-session -d -s mize-deploy "nix develop git+file://$SRC/mize#buildShell" @@ -898,8 +927,7 @@ pub fn main_deploy(_: &ArgMatches, _: Mize) -> MizeResult<()> { tmux send-keys -t mize-deploy "cargo build --manifest-path $SRC/mize/Cargo.toml --bin mize" Enter tmux send-keys -t mize-deploy 'echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" > /home/me/work/app-data/mize/daemon.env' Enter tmux send-keys -t mize-deploy "systemctl --user restart mize" Enter - "# - ); + "#; sh!(/bin/sh -c "$cmd")?; Ok(()) } diff --git a/marts/src/cli/mod.rs b/marts/src/cli/mod.rs index 118ae9b..a7286bc 100644 --- a/marts/src/cli/mod.rs +++ b/marts/src/cli/mod.rs @@ -84,7 +84,7 @@ pub fn cli(mize: &mut Mize) -> MizeResult<()> { )?; mize.add_listener( "cli.spawn_done", - Box::new(move |mut msg, mize| { + Box::new(move |_msg, _mize| { log::info!("before send"); spawn_wait_tx.send(()); log::info!("after send"); @@ -256,7 +256,7 @@ impl CliPart { action: T, ) -> MizeResult<()> { let name = subcmd.get_name().to_string(); - let mut cmd = self + let cmd = self .cmds .remove(&self.current_cmd_name) .unwrap() @@ -299,6 +299,7 @@ impl CliPart { } // impl smth like this to run cli commands in the daemon. +/* pub fn mize_thin_cli_client(bin_name: Option<&str>) -> MizeResult<()> { use std::io::Write; use std::os::unix::net::UnixStream; @@ -353,3 +354,4 @@ pub fn mize_thin_cli_client(bin_name: Option<&str>) -> MizeResult<()> { Ok(()) } + */ diff --git a/marts/src/dev/mod.rs b/marts/src/dev/mod.rs index 92cf172..55c049f 100644 --- a/marts/src/dev/mod.rs +++ b/marts/src/dev/mod.rs @@ -2,13 +2,13 @@ use mize::{Mize, MizeResult, MizeError, mize_err}; use crate::CliPart; +use clap::Arg; // spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?; pub fn part_dev(mize: &mut Mize) -> MizeResult<()> { let mut cli = mize.get_part_native::("cli")?; cli.set_command("dev"); - let bin_arg = Arg::new("bin").short('b'); diff --git a/marts/src/lib.rs b/marts/src/lib.rs index 41b991c..a42c944 100644 --- a/marts/src/lib.rs +++ b/marts/src/lib.rs @@ -38,7 +38,6 @@ pub use auth::*; pub mod ppc; -pub mod timetrack; #[cfg(feature = "target-obsidian")] pub mod matrix; diff --git a/marts/src/ppc/main_page.rs b/marts/src/ppc/main_page.rs deleted file mode 100644 index a5abd28..0000000 --- a/marts/src/ppc/main_page.rs +++ /dev/null @@ -1,194 +0,0 @@ - -use dioxus::prelude::*; -use axum::response::Html; - -pub fn main_page() -> Html { -let html_output = dioxus_ssr::render_element(rsx! { - div { - h1 { "Hello from Dioxus SSR!" } - p { "This was rendered on the server and sent via Axum." } - } - }); - - Html(html_output) -} - -pub fn main_page_element() -> Element { - rsx! { - "hello world" - div { - class: "min-h-screen bg-gray-100 items-center flex h-screen items-center justify-center", - - // Header - header { - class: "bg-blue-600 text-white py-4", - div { - class: "container mx-auto px-4", - h1 { - class: "text-2xl font-bold", - "People Corner (PPC)" - } - } - } - - // Main content - main { - class: "container mx-auto px-4 py-8", - - // Hero section - section { - class: "bg-white rounded-lg shadow-md p-6 mb-8", - h2 { - class: "text-xl font-semibold mb-4 text-blue-600", - "Welcome to People Corner" - } - p { - class: "text-gray-700 mb-4", - "PPC is a social platform for intentional people who want to get more out of life together." - } - p { - class: "text-gray-700", - "We're building a community-driven social media network that brings people together to do cool things, share resources, and support each other." - } - } - - // Mission section - section { - class: "bg-white rounded-lg shadow-md p-6 mb-8", - h2 { - class: "text-xl font-semibold mb-4 text-blue-600", - "Our Mission" - } - ul { - class: "list-disc pl-6 space-y-2", - li { "Bring people together to get more out of life" } - li { "Create meaningful connections beyond superficial social media" } - li { "Support intentional living and personal growth" } - li { "Build a platform by people, for people" } - li { "Foster real-world meetups and collaborations" } - li { "Create a social media for diligent, intentional people" } - } - } - - // Key concepts section - section { - class: "bg-white rounded-lg shadow-md p-6", - h2 { - class: "text-xl font-semibold mb-4 text-blue-600", - "Key Concepts" - } - ul { - class: "list-disc pl-6 space-y-2", - li { - strong { "Trust Network: " } - "A system for building and maintaining trust between members" - } - li { - strong { "Intentional Communities: " } - "Both online and physical spaces for like-minded people" - } - li { - strong { "Resource Sharing: " } - "Platform for sharing skills, knowledge, and physical resources" - } - li { - strong { "Real Connections: " } - "Focus on meaningful relationships over superficial interactions" - } - li { - strong { "Personal Growth: " } - "Support for individuals to achieve their life goals" - } - } - } - } - - // Footer - footer { - class: "bg-blue-600 text-white py-4 mt-8", - div { - class: "container mx-auto px-4 text-center", - p { "© 2024 People Corner. All rights reserved." } - } - } - } - } -} - -/// About page component -pub fn about_page() -> Element { - rsx! { - div { - class: "container mx-auto px-4 py-8", - h1 { - class: "text-2xl font-bold mb-4 text-blue-600", - "About PPC" - } - p { - class: "text-gray-700 mb-4", - "People Corner (PPC) is more than just a social platform - it's a movement to bring intentional people together to create meaningful connections and achieve more in life." - } - p { - class: "text-gray-700", - "Our vision is to build a world where people support each other, share resources, and grow together through real-world interactions and digital connections." - } - } - } -} - -/// Contact page component -pub fn contact_page() -> Element { - rsx! { - div { - class: "container mx-auto px-4 py-8", - h1 { - class: "text-2xl font-bold mb-4 text-blue-600", - "Contact Us" - } - p { - class: "text-gray-700 mb-4", - "We'd love to hear from you! Whether you have questions, feedback, or want to get involved, please reach out." - } - form { - class: "max-w-md space-y-4", - div { - label { - class: "block text-gray-700 mb-2", - "Name" - } - input { - class: "w-full px-3 py-2 border rounded-lg", - r#type: "text", - placeholder: "Your name" - } - } - div { - label { - class: "block text-gray-700 mb-2", - "Email" - } - input { - class: "w-full px-3 py-2 border rounded-lg", - r#type: "email", - placeholder: "Your email" - } - } - div { - label { - class: "block text-gray-700 mb-2", - "Message" - } - textarea { - class: "w-full px-3 py-2 border rounded-lg h-32", - placeholder: "Your message" - } - } - button { - class: "bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700", - r#type: "submit", - "Send Message" - } - } - } - } -} diff --git a/marts/src/ppc/mod.rs b/marts/src/ppc/mod.rs index d29e3ed..acad6ab 100644 --- a/marts/src/ppc/mod.rs +++ b/marts/src/ppc/mod.rs @@ -1,9 +1,199 @@ use std::str::FromStr; +use axum::response::Html; use clap::Command; use dioxus::prelude::*; use mize::Mize; use mize::MizeResult; pub mod server; -pub mod main_page; + +pub fn mc_page() -> Html { + Html(r#" + + + + + + + + PPC Minecraft Server + + + + + + +
+
+
Welcome to the
+
PPC
+
Minecraft Cluster
+
+
+
+ Address + first.ppc.social + Java 1.21.11 and Bedrock (bedrock not working rn) +
+
+ Discord + https://discord.gg/cMKQNJMkRe + come and say hi! +
+
+ See you ingame and invite all your friends to play as well!! +
+
+
+ SimpleVoicechat + + This is not required, but makes playing soooo much more fun, so it's + highly encouraged. +

+ Instructions can be found here:
+ Fabric +
+ SimpleVoiceChat +

+
+
+
+ + + "#.to_owned()) +} + +pub fn main_page() -> Html { + Html(r#" + + + + + + People Corner + + + + + + + +
+

"Cool People doing Cool things together"

+

Read what this is all about: About

+ +
+

There is a Discord Server: https://discord.gg/cMKQNJMkRe

+ +

There is a Minecraft server.

+ +

There is The PPC Wiki Projekt.

+ +

There is an Instagram and a Youtube Channel

+ +

and that's about all for now...

+ + + + "#.to_owned()) +} diff --git a/marts/src/ppc/server.rs b/marts/src/ppc/server.rs index bdfeea7..218572d 100644 --- a/marts/src/ppc/server.rs +++ b/marts/src/ppc/server.rs @@ -29,7 +29,7 @@ use tower_sessions::{ pub fn part_ppc_server(mize: &mut Mize) -> MizeResult<()> { mize.add_name_only_part("ppc.server"); - let mut mize_clone = mize.clone(); + let mize_clone = mize.clone(); mize.spawn_async("ppc.server_runner", start_server(mize_clone)); Ok(()) @@ -38,9 +38,9 @@ pub fn part_ppc_server(mize: &mut Mize) -> MizeResult<()> { pub async fn start_server(mut mize: Mize) -> MizeResult<()> { dioxus::logger::initialize_default(); - let issuer = mize.get_config("auth.issuer")?.value_string()?; - let client_id = mize.get_config("auth.client_id")?.value_string()?; - let client_secret = mize.get_config("auth.client_secret")?.value_string()?; + let issuer = mize.get_config("auth.server.issuer")?.value_string()?; + let client_id = mize.get_config("auth.server.client_id")?.value_string()?; + let client_secret = mize.get_config("auth.server.client_secret")?.value_string()?; let url = mize.get_config("web.url")?.value_string()?; let redirect_url = format!("{url}/oidc"); @@ -88,7 +88,8 @@ pub async fn start_server(mut mize: Mize) -> MizeResult<()> { .layer(session_layer) .layer(tower_http::trace::TraceLayer::new_for_http()) // not authenticated routes - .route("/", get(crate::ppc::main_page::main_page())) + .route("/", get(crate::ppc::main_page())) + .route("/mc", get(crate::ppc::mc_page())) // matrix .well-known .route( "/.well-known/matrix/client", @@ -101,7 +102,7 @@ pub async fn start_server(mut mize: Mize) -> MizeResult<()> { tracing::info!("Running on http://localhost:3000"); - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:8010").await.unwrap(); axum::serve(listener, app.into_make_service()) .await .unwrap(); @@ -140,6 +141,6 @@ async fn handle_matrix_client() -> Json { async fn handle_matrix_server() -> Json { Json(json!({ - "m.server": "://ppc.social" + "m.server": "matrix.ppc.social:443" })) } diff --git a/marts/src/sci/inbox/mod.rs b/marts/src/sci/inbox/mod.rs new file mode 100644 index 0000000..9014bf3 --- /dev/null +++ b/marts/src/sci/inbox/mod.rs @@ -0,0 +1,8 @@ + +pub use mize::{Mize, MizeResult, MizeError}; + +pub fn part_sci_inbox(mize: &mut Mize) -> MizeResult<()> { + + mize.add_name_only_part("sci.inbox"); + Ok(()) +} diff --git a/marts/src/sci/mod.rs b/marts/src/sci/mod.rs index 909faca..ef018ce 100644 --- a/marts/src/sci/mod.rs +++ b/marts/src/sci/mod.rs @@ -1,6 +1,7 @@ use mize::*; pub mod timetrack; +use clap::Command; pub fn part_sci(mize: &mut Mize) -> MizeResult<()> { part_add_thought(mize)?; @@ -9,20 +10,20 @@ pub fn part_sci(mize: &mut Mize) -> MizeResult<()> { } pub fn part_add_thought(mize: &mut Mize) -> MizeResult<()> { - use marts::generated::spacetime::add_thought; - let mut cli = mize.get_part_native::("cli")?; + use crate::generated::spacetime::add_thought; + let mut cli = mize.get_part_native::("cli")?; cli.set_command("sci"); - cli.subcommand(Command::new("inputThought").alias("ti"), |matches, mut mize| { + cli.subcommand(Command::new("inputThought").alias("ti"), |_matches, mut mize| { let tmp_file = std::env::temp_dir().join(format!( "sc2-input-thought-{}.txt", chrono::Utc::now().timestamp() )); - marts::cli_spawn(format!("nvim {}", tmp_file.display()))?; + crate::cli_spawn(&mut mize, format!("nvim {}", tmp_file.display()))?; let new_file_content = std::fs::read_to_string(&tmp_file) .map_err(|e| mize_err!("Failed to read temp file: {}", e))?; let _ = std::fs::remove_file(&tmp_file); - let spacetime = mize.get_part_native::("spacetime")?; + let spacetime = mize.get_part_native::("spacetime")?; spacetime.con().reducers.add_thought(new_file_content)?; Ok(()) })?; diff --git a/marts/src/sci/timetrack/mod.rs b/marts/src/sci/timetrack/mod.rs index 21fe636..bafbfa5 100644 --- a/marts/src/sci/timetrack/mod.rs +++ b/marts/src/sci/timetrack/mod.rs @@ -7,6 +7,15 @@ use mize::out; use mize::proto::MizeMessage; use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part}; use spacetimedb_sdk::{DbContext, Table}; +use generated::spacetime::export_timetrack; +use generated::spacetime::first_timetrack_date; +use std::fs::OpenOptions; +use chrono::Local; +use chrono::TimeDelta; +use std::io::Write; +use std::path::Path; +use std::fs; +use std::io; use generated::spacetime::{ Action, ActivitiesTableAccess, Activity, DbConnection, TaskPartTableAccess, @@ -149,7 +158,7 @@ impl MizePart for TimeTrack { #[cfg(feature = "target-os")] fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> { - let mut spacetime = mize.get_part_native::("spacetime")?; + let spacetime = mize.get_part_native::("spacetime")?; let con = spacetime.con(); let break_flag = matches.get_flag("break"); @@ -183,7 +192,7 @@ fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> { #[cfg(feature = "target-os")] fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { - let mut spacetime = mize.get_part_native::("spacetime")?; + let spacetime = mize.get_part_native::("spacetime")?; let action = get_current_action(&mut mize, spacetime.con())?; out!("{}", action.name); Ok(()) @@ -220,7 +229,7 @@ fn get_current_action(_mize: &mut Mize, con: &DbConnection) -> MizeResult MizeResult<()> { - let mut spacetime = mize.get_part_native::("spacetime")?; + let spacetime = mize.get_part_native::("spacetime")?; let activities = get_activities(&mut mize, spacetime.con())?; for ac in activities.clone() { @@ -239,7 +248,7 @@ fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { #[cfg(feature = "target-os")] fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { - let mut spacetime = mize.get_part_native::("spacetime")?; + let spacetime = mize.get_part_native::("spacetime")?; let actions = get_actions(spacetime.con())?; @@ -252,7 +261,7 @@ fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { #[cfg(feature = "target-os")] fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> { - let mut spacetime = mize.get_part_native::("spacetime")?; + let spacetime = mize.get_part_native::("spacetime")?; // maybe append a template to the action list if matches.get_flag("defaultTemplate") { @@ -353,7 +362,7 @@ fn parse_action(action: &mut Action) { #[cfg(feature = "target-os")] fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { - let mut spacetime = mize.get_part_native::("spacetime")?; + let spacetime = mize.get_part_native::("spacetime")?; // get list of activities let activities = get_activities(&mut mize, spacetime.con())?; @@ -546,18 +555,16 @@ fn parse_activities_from_text( } pub fn part_timetrack_export(mize: &mut Mize) -> MizeResult<()> { - use std::path::Path; - let mut cli = mize.get_part_native::("cli")?; + let mut cli = mize.get_part_native::("cli")?; - cli.set_command("ppc"); - cli.subcommand(Command::new("exportTimetrack").alias("et").arg(Arg::new("date").short('d')).arg(Arg::new("all").short('a').action(ArgAction::SetTrue)), |matches , mut mize| { - let spacetime = mize.get_part_native::("spacetime")?; - let notes_dir = mize.get_config("sc2.userNotesDir").unwrap_or_default().value_string()?; + cli.set_command("sci"); + cli.subcommand(Command::new("timetrackExport").alias("te").arg(Arg::new("date").short('d')).arg(Arg::new("all").short('a').action(ArgAction::SetTrue)), |matches , mut mize| { + let spacetime = mize.get_part_native::("spacetime")?; let today = Local::now().format("%Y-%m-%d").to_string(); let mut date = matches.get_one::("date").unwrap_or(&today); - std::fs::create_dir_all(Path::new(¬es_dir).join("timetrack_exports"))?; let all = matches.get_flag("all"); if all { + out!("exporting all not implemented rn"); let (tx, rx) = oneshot::channel(); spacetime.con().procedures.first_timetrack_date_then("c2vi".to_string(), |_, res| {tx.send(res);}); let res = rx.recv()???; @@ -567,23 +574,56 @@ pub fn part_timetrack_export(mize: &mut Mize) -> MizeResult<()> { let (tx, rx) = oneshot::channel(); spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {tx.send(res);}); let res = rx.recv()???; - let mut file = OpenOptions::new().create(true).write(true).open(Path::new(¬es_dir).join("timetrack_exports").join(format!("timetrack_export_{}.md", date)))?; - file.write_all(res.as_bytes())?; + //let mut file = OpenOptions::new().create(true).write(true).open(Path::new(¬es_dir).join("timetrack_exports").join(format!("timetrack_export_{}.md", date)))?; + //file.write_all(res.as_bytes())?; } return Ok(()); } out!("date: {}", date); let (tx, rx) = oneshot::channel(); - spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {tx.send(res);}); + spacetime.con().procedures.export_timetrack_then("c2vi".to_string(), date.to_owned(), |_, res| {let _ = tx.send(res);}); let res = rx.recv()???; - let mut file = OpenOptions::new().create(true).write(true).open(Path::new(¬es_dir).join("timetrack_exports").join(format!("timetrack_export_{}.md", date)))?; - file.write_all(res.as_bytes())?; + let daily_notes_path = mize.get_config("timetrack.dailyNotesPath")?.value_string()?; + let note_path = format!("{}/{}.md", daily_notes_path, date); + out!("writing to file: {}", note_path); + write_under_heading(note_path, "## timetrack export", res.as_ref())?; Ok(()) })?; Ok(()) } +fn write_under_heading( + file_path: impl AsRef, + heading: &str, + content: &str, +) -> io::Result<()> { + + let file_path = file_path.as_ref(); + let markdown = fs::read_to_string(file_path)?; + + let heading_line = format!("{}", heading); + let mut lines: Vec<&str> = markdown.lines().collect(); + + let index = lines + .iter() + .position(|line: &&str| line.trim() == heading_line) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::NotFound, + format!("Heading '{}' not found", heading), + ) + })?; + + lines.insert(index + 1, content); + + let mut updated = lines.join("\n"); + updated.push('\n'); + + fs::write(file_path, updated) +} + + fn dates_until_today(start: &str) -> Result, chrono::ParseError> { use chrono::NaiveDate; let mut current = NaiveDate::parse_from_str(start, "%Y-%m-%d")?;