updated README and a bunch of other stuff
This commit is contained in:
77
AGENTS.md
77
AGENTS.md
@@ -8,74 +8,17 @@ Mize is many things.
|
|||||||
- a library for COP = Configuration Oriented Programming
|
- a library for COP = Configuration Oriented Programming
|
||||||
- a thing providing one single API through which any data can be interacted with
|
- a thing providing one single API through which any data can be interacted with
|
||||||
|
|
||||||
|
All code written for, with or as part as mize (except for the small core) is written as a Mize Part. ALWAYS referr to:
|
||||||
|
@./docs/general-agent-guide.md
|
||||||
|
@./USING-MIZE-FOR-AGENTS.md
|
||||||
|
|
||||||
|
unless instructed otherwise put new parts into `./parts/vibed.<id-of-part>`.
|
||||||
|
|
||||||
## Codebase Structure
|
## Codebase Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
packages/
|
core/ # Core Mize framework (Rust)
|
||||||
├── src/ # Core Mize framework (Rust)
|
parts/ # Mize parts (cdylibs / crates)
|
||||||
├── marts/ # Mize parts collection (Rust/TypeScript)
|
macros/ # Proc macros
|
||||||
└── macros/ # Mize macros
|
docs/ # documentation
|
||||||
```
|
```
|
||||||
|
|
||||||
## Important
|
|
||||||
- usage of any git commands is strictly forbidden!
|
|
||||||
- in case it takes too long finding smth or figuring smth out. stop and ask the user. (goal is not to do stuff as autonomous as possible)
|
|
||||||
|
|
||||||
## 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`.
|
|
||||||
|
|
||||||
Any proc macros should go into `./macros`.
|
|
||||||
the core mize library is in `./core`
|
|
||||||
any mize parts are in `./parts` or in their own repositories.
|
|
||||||
|
|
||||||
## Require clarification and plan approval before making architectural code changes
|
|
||||||
|
|
||||||
Before making any code changes which could be considered to influence the architekture of the application, you must follow this two-step process:
|
|
||||||
|
|
||||||
### Step 1: Ask Clarifying Questions
|
|
||||||
|
|
||||||
- Always ask at least one clarifying question about the user's request
|
|
||||||
- Understand the full scope and context of what they're asking for
|
|
||||||
- Clarify any ambiguous requirements or edge cases
|
|
||||||
- Ask about preferred approaches if multiple solutions exist
|
|
||||||
- Confirm the expected behavior and user experience
|
|
||||||
- Ask the clarifying questions by numbering them, so the user can number their replies.
|
|
||||||
|
|
||||||
### Step 2: Present Implementation Plan
|
|
||||||
|
|
||||||
- After receiving clarification, present a detailed implementation plan
|
|
||||||
- Break down the work into specific, actionable steps
|
|
||||||
- Identify which files will be created, modified, or deleted
|
|
||||||
- Explain the technical approach and any architectural decisions
|
|
||||||
- Highlight any potential risks, trade-offs, or dependencies
|
|
||||||
- Estimate the complexity and scope of changes
|
|
||||||
- **Wait for explicit user approval** before proceeding with any code changes
|
|
||||||
|
|
||||||
### What are considered architectural changes
|
|
||||||
TODO: add better examples.
|
|
||||||
|
|
||||||
eg: adding a seccond line displaying a different value from the DB is not considered architectural. as it was achievable by only modifying the existing function and rsx! code.
|
|
||||||
|
|
||||||
### Approval Requirements
|
|
||||||
|
|
||||||
- User must explicitly approve the plan with words like "yes", "approved", "proceed", "go ahead", or similar
|
|
||||||
- If the user suggests modifications to the plan, incorporate them and seek re-approval
|
|
||||||
- Do not assume silence or ambiguous responses mean approval
|
|
||||||
|
|
||||||
### Exceptions
|
|
||||||
|
|
||||||
- This process may be skipped only for trivial changes like fixing obvious typos or formatting
|
|
||||||
- When in doubt, always follow the full process rather than assuming an exception applies
|
|
||||||
|
|
||||||
### Example Workflow
|
|
||||||
|
|
||||||
1. User: "Add a login form to the app"
|
|
||||||
2. Assistant: "I'd like to clarify a few things about the login form: [questions]"
|
|
||||||
3. User: [provides answers]
|
|
||||||
4. Assistant: "Based on your requirements, here's my implementation plan: [detailed plan]. Does this approach look good to you?"
|
|
||||||
5. User: "Yes, that looks good"
|
|
||||||
6. Assistant: [writes plan to file].
|
|
||||||
7. Assistant: [proceeds with implementation].
|
|
||||||
|
|||||||
241
Cargo.lock
generated
241
Cargo.lock
generated
@@ -141,6 +141,12 @@ dependencies = [
|
|||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_escape"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3df27b8d5ddb458c5fb1bbc1ce172d4a38c614a97d550b0ac89003897fb01de4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -304,6 +310,8 @@ dependencies = [
|
|||||||
"http 1.5.0",
|
"http 1.5.0",
|
||||||
"http-body 1.1.0",
|
"http-body 1.1.0",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
|
"hyper 1.12.0",
|
||||||
|
"hyper-util",
|
||||||
"itoa",
|
"itoa",
|
||||||
"matchit",
|
"matchit",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -316,9 +324,11 @@ dependencies = [
|
|||||||
"serde_path_to_error",
|
"serde_path_to_error",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -337,6 +347,28 @@ dependencies = [
|
|||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-oidc"
|
||||||
|
version = "1.0.0-dev-1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e035204cbe5a2275fbb47dd0c36e5111963da82a62ab7da05a515f7688951b3"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.5.0",
|
||||||
|
"openidconnect",
|
||||||
|
"reqwest 0.12.28",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.20",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions 0.14.0",
|
||||||
|
"tracing",
|
||||||
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1304,6 +1336,7 @@ dependencies = [
|
|||||||
"dioxus-html",
|
"dioxus-html",
|
||||||
"dioxus-logger",
|
"dioxus-logger",
|
||||||
"dioxus-signals",
|
"dioxus-signals",
|
||||||
|
"dioxus-ssr",
|
||||||
"dioxus-stores",
|
"dioxus-stores",
|
||||||
"dioxus-web",
|
"dioxus-web",
|
||||||
"manganis",
|
"manganis",
|
||||||
@@ -1787,6 +1820,18 @@ dependencies = [
|
|||||||
"warnings",
|
"warnings",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dioxus-ssr"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4544ecf908962909de403bfdf94f771b036e9e1857e371cd3279841a15a892e6"
|
||||||
|
dependencies = [
|
||||||
|
"askama_escape",
|
||||||
|
"dioxus-core",
|
||||||
|
"dioxus-core-types",
|
||||||
|
"rustc-hash 2.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-stores"
|
name = "dioxus-stores"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
@@ -3037,6 +3082,7 @@ dependencies = [
|
|||||||
"http 1.5.0",
|
"http 1.5.0",
|
||||||
"http-body 1.1.0",
|
"http-body 1.1.0",
|
||||||
"httparse",
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
@@ -3714,6 +3760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4043,7 +4090,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mize-part-dev"
|
name = "mize-part-mize-dev"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cmd_lib",
|
"cmd_lib",
|
||||||
@@ -4066,6 +4113,66 @@ dependencies = [
|
|||||||
"mize",
|
"mize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-part-mos-bash-config-cache"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"mize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-part-ppc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"mize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-part-ppc-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-oidc",
|
||||||
|
"dioxus",
|
||||||
|
"mize",
|
||||||
|
"openidconnect",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tower",
|
||||||
|
"tower-http 0.7.1",
|
||||||
|
"tower-sessions 0.15.0",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-part-sci"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"mize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-part-sci-inbox"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"mize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-part-sci-timetrack"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"mize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mize-part-spacetime"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"mize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mize_macros"
|
name = "mize_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -5629,7 +5736,7 @@ dependencies = [
|
|||||||
"tokio-rustls 0.26.6",
|
"tokio-rustls 0.26.6",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http 0.6.11",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -6911,6 +7018,23 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-cookies"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36"
|
||||||
|
dependencies = [
|
||||||
|
"axum-core",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.5.0",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6931,6 +7055,23 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"bytes",
|
||||||
|
"http 1.5.0",
|
||||||
|
"http-body 1.1.0",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -6943,12 +7084,102 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"http 1.5.0",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-cookies",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions-core 0.14.0",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "518dca34b74a17cadfcee06e616a09d2bd0c3984eff1769e1e76d58df978fc78"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"http 1.5.0",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-cookies",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions-core 0.15.0",
|
||||||
|
"tower-sessions-memory-store",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-core"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum-core",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"futures",
|
||||||
|
"http 1.5.0",
|
||||||
|
"parking_lot",
|
||||||
|
"rand 0.8.8",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.20",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-core"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "568531ec3dfcf3ffe493de1958ae5662a0284ac5d767476ecdb6a34ff8c6b06c"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum-core",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"futures",
|
||||||
|
"http 1.5.0",
|
||||||
|
"parking_lot",
|
||||||
|
"rand 0.9.5",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.20",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-memory-store"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "713fabf882b6560a831e2bbed6204048b35bdd60e50bbb722902c74f8df33460"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-sessions-core 0.15.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
@@ -7127,6 +7358,12 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|||||||
47
README.md
47
README.md
@@ -1,25 +1,32 @@
|
|||||||
# MiZe
|
# MiZe
|
||||||
With Mize I am building the software platform, that I would want to use.
|
|
||||||
|
|
||||||
_Elevate the Unix file philosophy into the age of connectivity._
|
website: https://mize.works
|
||||||
|
source: https://git.ppc.social/ppc/mize
|
||||||
|
matrix room: [#mize:ppc.social](https://matrix.to/#/#mize:ppc.social)
|
||||||
|
matrix room for updates: [#mize-updates:ppc.social](https://matrix.to/#/#mize-updates:ppc.social)
|
||||||
|
|
||||||
## Why am I doing this?
|
Mize is many things:
|
||||||
- What annoys me about software these days is, that things are done in so many different ways.
|
- a "strongly typed filesystem for the age of everyone having many devices"
|
||||||
- e.g: Different cloud applications for syncing data (Google Cloud, OneDrive, Dropbox)
|
- "eelevate the UNIX philosophy into the age of having multiple connected devices"
|
||||||
- e.g: Every app stores the data it deals with in it's own way (the way that the developer thought is best for this particular app)
|
- a new stdlib api to replace libc (but not just for C)
|
||||||
|
- 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 from any soruce can be interacted with
|
||||||
|
- a way to produce software with agents which is not slop
|
||||||
|
- (by making humans design architecture, agents implement code)
|
||||||
|
- (and providing a solid starter set of architecture for agents to use without humans)
|
||||||
|
- ([Mize Software Factory](https://wiki.ppc.social/Mize-Software-Factory))
|
||||||
|
- [c2vi](https://c2vi.dev) tinkering on the software he thinks is solid and would want to use
|
||||||
|
|
||||||
- Back when Unix came out **everything** was stored in one place: the file system of the computer.
|
## things
|
||||||
- (just as simple bytestreams/files, organized by a folder-structure)
|
- [MiZe Parts](https://wiki.ppc.social/u/c2vi/MiZe-Part)
|
||||||
- and NO program stored and synced any data around in unpredictable ways.
|
- [MiZe Init System](https://wiki.ppc.social/u/c2vi/MiZe-Init-System)
|
||||||
|
- [Config Oriented Programming|COP = Config Oriented Programming](https://wiki.ppc.social/u/c2vi/Config-Oriented-Programming)
|
||||||
- This philosophy is still used in every Unix/Linux machine, on a local level, but completely abstracted away (especially on mobile phones) from the user (by every app).
|
- [mize lang](https://wiki.ppc.social/u/c2vi/mize-lang)
|
||||||
|
- [Mize Software Factory](https://wiki.ppc.social/Mize-Software-Factory)
|
||||||
- I would say: The Unix file system just hasn't evolved into the age of connectivity. It is still completely the same. And I think it shouldn't be.
|
|
||||||
|
|
||||||
- MiZe aims to bring that idea of storing all data
|
|
||||||
- in one place
|
|
||||||
- in one predictable and simple way
|
|
||||||
- and programs only taking data from there and not storing it everywhere in messy ways
|
|
||||||
|
|
||||||
- That's why the slogan is going to be: _Elevate the unix file philosophy into the age of connectivity._
|
|
||||||
|
|
||||||
|
- [The Idea of MiZe](https://wiki.ppc.social/u/c2vi/The-Idea-of-MiZe)
|
||||||
|
- [How the idea of MiZe came to be](https://wiki.ppc.social/u/c2vi/How-the-idea-of-MiZe-came-to-be)
|
||||||
|
- [The WHY of Mize](https://wiki.ppc.social/u/c2vi/The-WHY-of-Mize)
|
||||||
|
- [How MiZe works](https://wiki.ppc.social/u/c2vi/How-MiZe-works) (quite old article, probably no longer fully accurate)
|
||||||
|
|||||||
72
USING-MIZE-FOR-AGENTS.md
Normal file
72
USING-MIZE-FOR-AGENTS.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
# The Mize software factory framework.
|
||||||
|
goal: make clean/sane, configurable, extensible, **maintanable**, **human-comprehendable**, debuggable, tracable, pluggable, **well-architectured**, ... software usuing agentic engineering.
|
||||||
|
|
||||||
|
@./docs/general-agent-guide.md
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Mize is built around the **parts architecture** which allows you to compose anc configure functionality modularly. Each part is a self-contained unit that:
|
||||||
|
|
||||||
|
- can store and manage its own state
|
||||||
|
- can expose methods to other parts
|
||||||
|
- Registers itself with the Mize instance
|
||||||
|
- Defines configuration options
|
||||||
|
- define UI components (called presenters)
|
||||||
|
|
||||||
|
Parts are the primary way to build any new software using this framework.
|
||||||
|
|
||||||
|
## conditions when to stop and check back in with the human (IMPORTANT).
|
||||||
|
- dealing with/adding dependencies on system libraries or packages is also something to stop for and ask the user how to do it.
|
||||||
|
- in case it takes too long finding smth or figuring smth out. stop and ask the user. (goal is not to do stuff as autonomous as possible)
|
||||||
|
- **Stop on architectural dead-ends.** If something does not work out architecture-wise — e.g. one part needs to notify another of a state change, but there is no elegant existing mechanism (`method` / `opt` / `has_part` / listeners / etc.) — do **not** invent a hacky workaround and push on. Stop, describe the gap and the options you see, and ask the user how to proceed.
|
||||||
|
|
||||||
|
|
||||||
|
## some other notes
|
||||||
|
- Prefer expanding an existing part over creating a new one when it fits — but ask user before expanding.
|
||||||
|
|
||||||
|
- when developing a new part. present it's config options and methods to the human first.
|
||||||
|
|
||||||
|
When turning a design / whole application into parts, follow **Splitting an app into parts**.
|
||||||
|
|
||||||
|
## Splitting an app into parts
|
||||||
|
|
||||||
|
A part is a **named capability with its own config namespace and callable surface** — not a technical layer (UI/API/DB) and not a folder dump of screens.
|
||||||
|
|
||||||
|
When turning a design document or whole application into parts:
|
||||||
|
|
||||||
|
1. **One capability, one part** — Group requirements that share one job and vocabulary (login/tokens, inbox, hub shell). That cluster becomes one part id (`auth.client`, `mme`, `sci.inbox`).
|
||||||
|
2. **Config namespace = part boundary** — Settings that naturally sit under one prefix (`auth.client.issuer`, …) belong to that part. Two unrelated prefixes → two parts.
|
||||||
|
3. **Methods are the public API** — Expose capability-shaped methods (`login`, `set_login_state`), not “whatever the dashboard clicked.” CLI and other parts should call the same `method`s via `dyn_call` / generated bindings — don’t use CLI `run` as the only inter-part API.
|
||||||
|
4. **Product / UI shells are thin** — Dashboards own layout and presentation. Domain work stays in capability parts. Optional UI notify via `has_part("…")` + `dyn_call` so headless/CLI still works.
|
||||||
|
5. **Split on replaceability or reuse** — Would you swap the IdP but keep the hub? Second app needs login without the hub? Keep that capability separate. Only this screen will ever use X? Keep X in the shell until a second consumer appears.
|
||||||
|
6. **Don’t split only by layer** — Avoid default `foo.ui` + `foo.logic`. Prefer `foo` (capability) + optional shell. Shared widgets can be a **library** (e.g. `dioxus-components`) until they need mize opts/methods.
|
||||||
|
7. **Hard vs soft dependency** — Hard: cannot run without the other (load order / deps). Soft: “if UI is loaded, push URL/code.” Soft deps must not break headless use.
|
||||||
|
8. **Lifecycle is a boundary smell** — Background refresh, blocking window, long device poll with different lifetimes often means the domain side should be its own part with clear methods.
|
||||||
|
9. **Start coarse; split on friction** — Prefer fewer parts from a design doc. Split when a second consumer appears, config namespaces diverge, or you need to enable one capability without another.
|
||||||
|
10. **Before coding, present a part table** — For each proposed part: `id`, one-sentence responsibility, `opt`s, `method`s, hard deps, soft notifies. Present that to the human first (same rule as presenting config options and methods).
|
||||||
|
|
||||||
|
|
||||||
|
## UI work
|
||||||
|
For any UI work:
|
||||||
|
- Use **Dioxus** (desktop unless the user says otherwise).
|
||||||
|
- Build with **`parts/mize.ui/dioxus-components`** — reuse existing components (`Button`, `Prompt`, `Control`, …) and extend that library when something reusable is missing; don’t invent a parallel widget set.
|
||||||
|
- Prefer **inlined CSS** via `OnceStyle` / isolated style components (see that crate). Do **not** put `document::Style` inside components that re-render often; don’t bring back `asset!` / css-modules for these components unless the user asks.
|
||||||
|
- Don’t block the Dioxus UI thread on long part work (login flows, etc.): background thread / async + part `method`s updating shared state the UI reads.
|
||||||
|
|
||||||
|
unless specified diferently any mize UI uses the PPC color pallete (derived from the PPC Logo: https://wiki.ppc.social/PPC-Logo):
|
||||||
|
```
|
||||||
|
Token Light mode Dark mode
|
||||||
|
Background #F7F8F4 #171D18
|
||||||
|
Surface #FFFFFF #242D25
|
||||||
|
Surface secondary #EFF2EA #2C362D
|
||||||
|
Primary text #20251C #F2F4EA
|
||||||
|
Secondary text #66705E #AAB5A4
|
||||||
|
Border #DFE4D9 #3A463A
|
||||||
|
Primary accent #FEC251 #D7E47D
|
||||||
|
Accent text #79500F #D7E47D
|
||||||
|
Success #526F32 #B9D987
|
||||||
|
Info #376777 #B9E4EB
|
||||||
|
Warning #A66B13 #FFD77F
|
||||||
|
Danger #B94D42 #FF9B8E
|
||||||
|
```
|
||||||
@@ -26,21 +26,16 @@ pub fn get(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
|||||||
.get_one::<String>("id")
|
.get_one::<String>("id")
|
||||||
.ok_or(MizeError::new().msg("No id Argument specified"))?;
|
.ok_or(MizeError::new().msg("No id Argument specified"))?;
|
||||||
|
|
||||||
let item = instance.get(id)?;
|
let raw = sub_matches.get_flag("raw");
|
||||||
|
let data = instance.get_config(id)?;
|
||||||
|
|
||||||
let recurse_count = sub_matches.get_count("recurse");
|
if raw {
|
||||||
|
print!("{}", data.value_string()?);
|
||||||
println!("{}", item.as_data_full()?);
|
|
||||||
return Ok(());
|
|
||||||
|
|
||||||
if recurse_count > 0 {
|
|
||||||
println!("{}", item.id());
|
|
||||||
println!("{}", item.as_data_full()?);
|
|
||||||
} else {
|
} else {
|
||||||
println!("{}", item);
|
println!("{}", data);
|
||||||
};
|
}
|
||||||
|
|
||||||
return Ok(());
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
pub fn call(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||||
|
|||||||
@@ -147,7 +147,13 @@ fn cli_matches() -> clap::ArgMatches {
|
|||||||
Command::new("get")
|
Command::new("get")
|
||||||
.aliases(["g"])
|
.aliases(["g"])
|
||||||
.arg(Arg::new("id").help("The id or path to get"))
|
.arg(Arg::new("id").help("The id or path to get"))
|
||||||
.arg(Arg::new("recurse").short('r').action(ArgAction::Count)),
|
.arg(Arg::new("recurse").short('r').action(ArgAction::Count))
|
||||||
|
.arg(
|
||||||
|
Arg::new("raw")
|
||||||
|
.long("raw")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.help("print the config opt value as a raw string"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("set")
|
Command::new("set")
|
||||||
|
|||||||
@@ -1,3 +1,255 @@
|
|||||||
|
//! C ABI for dynamically loaded parts (see `include/mize.h`).
|
||||||
|
//!
|
||||||
|
//! Names passed to `opt` / `method` / `thread` / `get_config` / `set_config`
|
||||||
|
//! are absolute dotted paths (e.g. `"vicinae.open"`).
|
||||||
|
//!
|
||||||
|
//! Errors use a thread-local slot (like errno): C code calls `set_err(...)`;
|
||||||
|
//! Rust checks via [`take_err`] after the call.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::{c_char, c_void, CStr};
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
//pub extern "C" fn
|
use crate::config::{ConfigOpt, MizeFun};
|
||||||
|
use crate::error::MizeError;
|
||||||
|
use crate::get_global_mize;
|
||||||
|
use crate::item::ItemData;
|
||||||
|
use crate::mize_err;
|
||||||
|
use crate::write_to_mize_stdout;
|
||||||
|
|
||||||
|
type CFun = unsafe extern "C" fn(*mut ItemData) -> *mut ItemData;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// Per-thread error set by C via `set_err`, consumed by Rust after the call.
|
||||||
|
static CUR_ERR: RefCell<Option<MizeError>> = const { RefCell::new(None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the thread-local C error (call before invoking a C part entry / callback).
|
||||||
|
pub fn clear_err() {
|
||||||
|
CUR_ERR.with(|slot| {
|
||||||
|
*slot.borrow_mut() = None;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take the thread-local C error, if any.
|
||||||
|
pub fn take_err() -> Option<MizeError> {
|
||||||
|
CUR_ERR.with(|slot| slot.borrow_mut().take())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opaque pointer stored via `data_from_native` / read via `data_as_native`.
|
||||||
|
struct NativePtr(*mut c_void);
|
||||||
|
|
||||||
|
// SAFETY: the C++ part is responsible for only using the pointer on the right thread.
|
||||||
|
unsafe impl Send for NativePtr {}
|
||||||
|
unsafe impl Sync for NativePtr {}
|
||||||
|
|
||||||
|
fn cstr<'a>(p: *const c_char) -> Option<&'a str> {
|
||||||
|
if p.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
unsafe { CStr::from_ptr(p) }.to_str().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_c_fun(c_fun: CFun) -> MizeFun {
|
||||||
|
Box::new(move |args| {
|
||||||
|
clear_err();
|
||||||
|
let mut args_box = Box::new(args);
|
||||||
|
let ret_ptr = unsafe { c_fun(args_box.as_mut()) };
|
||||||
|
drop(args_box);
|
||||||
|
if let Some(err) = take_err() {
|
||||||
|
if !ret_ptr.is_null() {
|
||||||
|
unsafe { drop(Box::from_raw(ret_ptr)) };
|
||||||
|
}
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
if ret_ptr.is_null() {
|
||||||
|
return Ok(ItemData::new());
|
||||||
|
}
|
||||||
|
Ok(*unsafe { Box::from_raw(ret_ptr) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_fun(name: &str, kind: &str, signature: &str, fun: MizeFun) {
|
||||||
|
let mut mize = match get_global_mize() {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = write_to_mize_stdout(format_args!("[c_api] get_global_mize failed: {e:?}\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = match mize.id_new_dots(name) {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = write_to_mize_stdout(format_args!("[c_api] bad id '{name}': {e:?}\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if mize.is_generate_mode().unwrap_or(false) {
|
||||||
|
let short = name.rsplit('.').next().unwrap_or(name);
|
||||||
|
let _ = mize.record_method(short, id.clone(), kind, signature);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt = ConfigOpt {
|
||||||
|
name: id.dot(),
|
||||||
|
val: None,
|
||||||
|
fun: None,
|
||||||
|
deps: Vec::new(),
|
||||||
|
};
|
||||||
|
mize.config_opts.lock().unwrap().insert(id.clone(), opt);
|
||||||
|
mize.fun_values.lock().unwrap().insert(id, fun);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn opt(name: *const c_char) {
|
||||||
|
let Some(name) = cstr(name) else { return };
|
||||||
|
let mut mize = match get_global_mize() {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let Ok(id) = mize.id_new_dots(name) else { return };
|
||||||
|
|
||||||
|
if mize.is_generate_mode().unwrap_or(false) {
|
||||||
|
let short = name.rsplit('.').next().unwrap_or(name);
|
||||||
|
let _ = mize.record_opt(short, id.clone(), None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt = ConfigOpt {
|
||||||
|
name: id.dot(),
|
||||||
|
val: None,
|
||||||
|
fun: None,
|
||||||
|
deps: Vec::new(),
|
||||||
|
};
|
||||||
|
mize.config_opts.lock().unwrap().insert(id, opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn method(name: *const c_char, signature: *const c_char, c_fun: CFun) {
|
||||||
|
let Some(name) = cstr(name) else { return };
|
||||||
|
let sig = cstr(signature).unwrap_or("");
|
||||||
|
register_fun(name, "method", sig, wrap_c_fun(c_fun));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn effect(name: *const c_char, c_fun: CFun) {
|
||||||
|
let Some(name) = cstr(name) else { return };
|
||||||
|
register_fun(name, "effect", "", wrap_c_fun(c_fun));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn apply(name: *const c_char, c_fun: CFun) {
|
||||||
|
let Some(name) = cstr(name) else { return };
|
||||||
|
register_fun(name, "apply", "", wrap_c_fun(c_fun));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) {
|
||||||
|
let Some(name) = cstr(name) else { return };
|
||||||
|
register_fun(name, "thread", "", wrap_c_fun(c_fun));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn set_config(name: *const c_char, value: *mut ItemData) {
|
||||||
|
if value.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(name) = cstr(name) else {
|
||||||
|
unsafe { drop(Box::from_raw(value)) };
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let value = unsafe { *Box::from_raw(value) };
|
||||||
|
let mut mize = match get_global_mize() {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let Ok(id) = mize.id_new_dots(name) else { return };
|
||||||
|
|
||||||
|
let mut opts = mize.config_opts.lock().unwrap();
|
||||||
|
match opts.get_mut(&id) {
|
||||||
|
Some(opt) => {
|
||||||
|
opt.val = Some(value);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
opts.insert(
|
||||||
|
id.clone(),
|
||||||
|
ConfigOpt {
|
||||||
|
name: id.dot(),
|
||||||
|
val: Some(value),
|
||||||
|
fun: None,
|
||||||
|
deps: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn get_config(name: *const c_char) -> *mut ItemData {
|
||||||
|
let Some(name) = cstr(name) else {
|
||||||
|
return ptr::null_mut();
|
||||||
|
};
|
||||||
|
let mut mize = match get_global_mize() {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(_) => return ptr::null_mut(),
|
||||||
|
};
|
||||||
|
match mize.get_config(name) {
|
||||||
|
Ok(data) => Box::into_raw(Box::new(data)),
|
||||||
|
Err(_) => ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn data_new() -> *mut ItemData {
|
||||||
|
Box::into_raw(Box::new(ItemData::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn data_from_native(ptr: *mut c_void) -> *mut ItemData {
|
||||||
|
match ItemData::from_native(NativePtr(ptr)) {
|
||||||
|
Ok(data) => Box::into_raw(Box::new(data)),
|
||||||
|
Err(e) => {
|
||||||
|
let _ = write_to_mize_stdout(format_args!("[c_api] data_from_native: {e:?}\n"));
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn data_as_native(data: *mut ItemData) -> *mut c_void {
|
||||||
|
if data.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let data = unsafe { &*data };
|
||||||
|
match data.native::<NativePtr>() {
|
||||||
|
Ok(guard) => guard.0,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = write_to_mize_stdout(format_args!("[c_api] data_as_native: {e:?}\n"));
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn data_free(data: *mut ItemData) {
|
||||||
|
if !data.is_null() {
|
||||||
|
unsafe { drop(Box::from_raw(data)) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn mize_write_stdout(str_ptr: *const c_char) {
|
||||||
|
let Some(s) = cstr(str_ptr) else { return };
|
||||||
|
let _ = write_to_mize_stdout(format_args!("{s}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the thread-local error (like errno). `load_part` / C-callback wrappers turn this into `Err`.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn set_err(msg: *const c_char) {
|
||||||
|
let text = cstr(msg).unwrap_or("set_err called with null/invalid message");
|
||||||
|
CUR_ERR.with(|slot| {
|
||||||
|
*slot.borrow_mut() = Some(mize_err!("{}", text));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,6 +98,17 @@ impl<T> MizeAny for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Load the `.so` for `part_name`, but register it under `at_id`
|
||||||
|
/// (e.g. `load_part_at("priv.spacetime", "spacetime")`).
|
||||||
|
/// No-op in generate mode — bindings come from listing the part in `gen = [...]`.
|
||||||
|
pub fn load_part_at(at_id: &str, part_name: &str) -> MizeResult<()> {
|
||||||
|
let mize = get_global_mize()?;
|
||||||
|
if mize.is_generate_mode().unwrap_or(false) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
crate::platform::load_part(part_name, None, Some(at_id))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn opt(name: &str) -> crate::config::ConfigOptId {
|
pub fn opt(name: &str) -> crate::config::ConfigOptId {
|
||||||
let mize = get_global_mize().unwrap();
|
let mize = get_global_mize().unwrap();
|
||||||
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
||||||
@@ -162,6 +173,28 @@ pub fn effect<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(n
|
|||||||
ConfigOptId { id }
|
ConfigOptId { id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
|
||||||
|
let mize = get_global_mize().unwrap();
|
||||||
|
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
||||||
|
let id = cur_part.join(name).unwrap();
|
||||||
|
// in generate mode: record apply metadata, skip normal registration
|
||||||
|
if mize.is_generate_mode().unwrap_or(false) {
|
||||||
|
mize.record_method(name, id.clone(), "apply", "").ok();
|
||||||
|
return ConfigOptId { id };
|
||||||
|
}
|
||||||
|
let mut opts = mize.config_opts.lock().unwrap();
|
||||||
|
let opt = ConfigOpt {
|
||||||
|
name: id.dot(),
|
||||||
|
val: None,
|
||||||
|
fun: None,
|
||||||
|
deps: Vec::new(),
|
||||||
|
};
|
||||||
|
let mut fun_values = mize.fun_values.lock().unwrap();
|
||||||
|
fun_values.insert(id.clone(), Box::new(fun));
|
||||||
|
opts.insert(id.clone(), opt);
|
||||||
|
ConfigOptId { id }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn thread<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
|
pub fn thread<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
|
||||||
let mize = get_global_mize().unwrap();
|
let mize = get_global_mize().unwrap();
|
||||||
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
||||||
@@ -227,6 +260,18 @@ pub mod c_api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub extern "C" fn apply(name: *const c_char, c_fun: CFun) -> ConfigOptId {
|
||||||
|
unsafe {
|
||||||
|
let c_str = CStr::from_ptr(name);
|
||||||
|
let string = c_str.to_string_lossy();
|
||||||
|
super::apply(&string, move |args| {
|
||||||
|
let raw_ptr = Box::into_raw(Box::new(args));
|
||||||
|
let ret_ptr = c_fun(raw_ptr);
|
||||||
|
let data = *Box::from_raw(ret_ptr);
|
||||||
|
Ok(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) -> ConfigOptId {
|
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) -> ConfigOptId {
|
||||||
unsafe {
|
unsafe {
|
||||||
let c_str = CStr::from_ptr(name);
|
let c_str = CStr::from_ptr(name);
|
||||||
|
|||||||
61
core/src/include/mize.h
Normal file
61
core/src/include/mize.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mize C API — thin header over libmize.so exports (core/src/c_api.rs).
|
||||||
|
*
|
||||||
|
* Names for opt / method / thread / get_config / set_config are absolute
|
||||||
|
* dotted paths for now (e.g. "vicinae.open", "vicinae.nav").
|
||||||
|
*
|
||||||
|
* Errors: thread-local, like errno. Call set_err("...") from C; Rust
|
||||||
|
* (load_part / method wrappers) turns that into Err. Part entry is
|
||||||
|
* `void part_<name>(void)` — no sret / Result return.
|
||||||
|
*
|
||||||
|
* ItemData (`DATA`) is opaque. Ownership:
|
||||||
|
* - Callbacks receive a borrowed `args` (do not free).
|
||||||
|
* - Callbacks must return an owned DATA* (e.g. data_new()); may return NULL for empty.
|
||||||
|
* - get_config returns owned DATA*; free with data_free.
|
||||||
|
* - set_config takes ownership of the value pointer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct ItemData DATA;
|
||||||
|
|
||||||
|
/** C method / thread / effect callback. args is borrowed; return owned DATA* or NULL. */
|
||||||
|
typedef DATA *(*CFun)(DATA *args);
|
||||||
|
|
||||||
|
void opt(const char *name);
|
||||||
|
|
||||||
|
void method(const char *name, const char *signature, CFun c_fun);
|
||||||
|
|
||||||
|
void effect(const char *name, CFun c_fun);
|
||||||
|
|
||||||
|
void thread(const char *name, CFun c_fun);
|
||||||
|
|
||||||
|
void set_config(const char *name, DATA *value);
|
||||||
|
|
||||||
|
DATA *get_config(const char *name);
|
||||||
|
|
||||||
|
DATA *data_new(void);
|
||||||
|
|
||||||
|
/** Store an opaque pointer in the native-value registry. */
|
||||||
|
DATA *data_from_native(void *ptr);
|
||||||
|
|
||||||
|
/** Read back a pointer previously stored with data_from_native. Returns NULL on error. */
|
||||||
|
void *data_as_native(DATA *data);
|
||||||
|
|
||||||
|
void data_free(DATA *data);
|
||||||
|
|
||||||
|
void mize_write_stdout(const char *str);
|
||||||
|
|
||||||
|
/** Set the thread-local error message (consumed by Rust after the call). */
|
||||||
|
void set_err(const char *msg);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -105,6 +105,7 @@ pub struct PartMetadata {
|
|||||||
pub opts: Vec<OptMetadata>,
|
pub opts: Vec<OptMetadata>,
|
||||||
pub methods: Vec<MethodMetadata>,
|
pub methods: Vec<MethodMetadata>,
|
||||||
pub effects: Vec<MethodMetadata>,
|
pub effects: Vec<MethodMetadata>,
|
||||||
|
pub applies: Vec<MethodMetadata>,
|
||||||
pub threads: Vec<MethodMetadata>,
|
pub threads: Vec<MethodMetadata>,
|
||||||
pub natives: Vec<NativeMetadata>,
|
pub natives: Vec<NativeMetadata>,
|
||||||
}
|
}
|
||||||
@@ -120,7 +121,7 @@ pub struct OptMetadata {
|
|||||||
pub struct MethodMetadata {
|
pub struct MethodMetadata {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub id: MizeId,
|
pub id: MizeId,
|
||||||
pub kind: String, // "method", "effect", "thread"
|
pub kind: String, // "method", "effect", "apply", "thread"
|
||||||
/// Raw signature string, e.g. `"String -> ()"` or `"Command, Func -> ()"`.
|
/// Raw signature string, e.g. `"String -> ()"` or `"Command, Func -> ()"`.
|
||||||
pub signature: String,
|
pub signature: String,
|
||||||
/// Parsed argument type names (left of `->`).
|
/// Parsed argument type names (left of `->`).
|
||||||
@@ -339,20 +340,23 @@ impl Mize {
|
|||||||
// end of platform specific init code
|
// end of platform specific init code
|
||||||
|
|
||||||
// load the parts, as specified in the parts config
|
// load the parts, as specified in the parts config
|
||||||
match self
|
// Prefer `parts` when it is an array; otherwise `parts.load` (so `parts.workspaces` /
|
||||||
.get_config("parts")
|
// `parts.dist.cd` can coexist under a `parts` table in TOML).
|
||||||
{
|
let parts_to_load = match self.get_config("parts") {
|
||||||
Ok(parts_to_load) => {
|
Ok(data) if data.cbor().as_array().is_some() => Some(data),
|
||||||
for part_name in parts_to_load.cbor().as_array().ok_or("config parts was not an array")? {
|
_ => self.get_config("parts.load").ok(),
|
||||||
let part_name = part_name.as_text().ok_or("part name was not a string")?;
|
};
|
||||||
if part_name.trim().is_empty() || self.has_part(part_name)? {
|
if let Some(parts_to_load) = parts_to_load {
|
||||||
continue;
|
for part_name in parts_to_load
|
||||||
}
|
.cbor()
|
||||||
self.load_part(part_name)?;
|
.as_array()
|
||||||
|
.ok_or("config parts/parts.load was not an array")?
|
||||||
|
{
|
||||||
|
let part_name = part_name.as_text().ok_or("part name was not a string")?;
|
||||||
|
if part_name.trim().is_empty() || self.has_part(part_name)? {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
self.load_part(part_name)?;
|
||||||
Err(_err) => {
|
|
||||||
// no parts to load
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,7 +538,7 @@ impl Mize {
|
|||||||
|
|
||||||
pub fn load_part(&mut self, name: &str) -> MizeResult<()> {
|
pub fn load_part(&mut self, name: &str) -> MizeResult<()> {
|
||||||
tracing::debug!("loading part: {name}");
|
tracing::debug!("loading part: {name}");
|
||||||
crate::platform::load_part(self, name, None)
|
crate::platform::load_part(name, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_part(&mut self, name: &str) -> MizeResult<String> {
|
pub fn fetch_part(&mut self, name: &str) -> MizeResult<String> {
|
||||||
@@ -542,9 +546,8 @@ impl Mize {
|
|||||||
crate::platform::fetch_part(self, name)
|
crate::platform::fetch_part(self, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_part_at(&mut self, name: &str, path: String) -> MizeResult<()> {
|
pub fn load_part_from_path(&mut self, name: &str, path: String) -> MizeResult<()> {
|
||||||
// platform specific init code
|
crate::platform::load_part(name, Some(path), None)
|
||||||
crate::platform::load_part(self, name, Some(path))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== generate mode accessors =====
|
// ===== generate mode accessors =====
|
||||||
@@ -600,6 +603,7 @@ impl Mize {
|
|||||||
};
|
};
|
||||||
match kind {
|
match kind {
|
||||||
"effect" => metadata.effects.push(m),
|
"effect" => metadata.effects.push(m),
|
||||||
|
"apply" => metadata.applies.push(m),
|
||||||
"thread" => metadata.threads.push(m),
|
"thread" => metadata.threads.push(m),
|
||||||
_ => metadata.methods.push(m),
|
_ => metadata.methods.push(m),
|
||||||
}
|
}
|
||||||
@@ -1037,9 +1041,7 @@ impl Mize {
|
|||||||
|
|
||||||
let calling_part = self.clone().cur_eval_part()?;
|
let calling_part = self.clone().cur_eval_part()?;
|
||||||
self.clone().set_cur_eval_part(Some(part_id));
|
self.clone().set_cur_eval_part(Some(part_id));
|
||||||
println!("before call");
|
|
||||||
let res = fun(args);
|
let res = fun(args);
|
||||||
println!("after call");
|
|
||||||
self.clone().set_cur_eval_part(calling_part);
|
self.clone().set_cur_eval_part(calling_part);
|
||||||
let mut fun_values = self.fun_values.lock()?;
|
let mut fun_values = self.fun_values.lock()?;
|
||||||
fun_values.insert(id, fun);
|
fun_values.insert(id, fun);
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ fn test_get_module_hash() -> MizeResult<()> {
|
|||||||
toolchain_version = "rustc 1.78.0 (9b00956e5 2024-04-29)"
|
toolchain_version = "rustc 1.78.0 (9b00956e5 2024-04-29)"
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
instance.set_blocking("0/config/selector", ItemData::from_toml(&selector_toml)?)?;
|
// Pass selector fields as hash options (merges over defaults).
|
||||||
|
let options = ItemData::from_toml(&selector_toml)?;
|
||||||
let (hash, selector) = get_part_hash(&mut instance, "String", ItemData::new())?;
|
let (hash, selector) = get_part_hash(&mut instance, "String", options)?;
|
||||||
|
|
||||||
println!("hash: {}", hash);
|
println!("hash: {}", hash);
|
||||||
println!("selector: {}", selector);
|
println!("selector: {}", selector);
|
||||||
|
|
||||||
assert_eq!(hash, "e96edf60216688314efc263c1aa8d6ce".to_owned());
|
assert_eq!(hash, "227b0216f9b7a324a621ff773c5241c9".to_owned());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,6 +238,9 @@ impl ItemData {
|
|||||||
pub fn value_string(self) -> MizeResult<String> {
|
pub fn value_string(self) -> MizeResult<String> {
|
||||||
match self.cbor() {
|
match self.cbor() {
|
||||||
CborValue::Text(string) => Ok(string.to_owned()),
|
CborValue::Text(string) => Ok(string.to_owned()),
|
||||||
|
CborValue::Bool(val) => Ok(format!("{val}")),
|
||||||
|
CborValue::Integer(val) => Ok(format!("{}", u32::try_from(val.clone())?)),
|
||||||
|
CborValue::Float(val) => Ok(format!("{val}")),
|
||||||
_ => Err(mize_err!("this ItemData has no string value")),
|
_ => Err(mize_err!("this ItemData has no string value")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,9 +292,9 @@ pub mod platform {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> MizeResult<()> {
|
pub fn load_part(part_name: &str, path: Option<String>, at_id: Option<&str>) -> MizeResult<()> {
|
||||||
#[cfg(feature = "target-os")] {
|
#[cfg(feature = "target-os")] {
|
||||||
os::load_part(mize, part_name, path)?;
|
os::load_part(part_name, path, at_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -171,12 +171,158 @@ pub fn seconds_since_modification(path: &Path) -> MizeResult<u64> {
|
|||||||
Ok(duration.as_secs())
|
Ok(duration.as_secs())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Best-effort rustc target triple for the currently running binary.
|
||||||
|
pub fn host_rust_target() -> &'static str {
|
||||||
|
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
|
||||||
|
{
|
||||||
|
"x86_64-unknown-linux-gnu"
|
||||||
|
}
|
||||||
|
#[cfg(all(target_arch = "aarch64", target_os = "linux", target_env = "gnu"))]
|
||||||
|
{
|
||||||
|
"aarch64-unknown-linux-gnu"
|
||||||
|
}
|
||||||
|
#[cfg(all(target_arch = "x86_64", target_os = "macos"))]
|
||||||
|
{
|
||||||
|
"x86_64-apple-darwin"
|
||||||
|
}
|
||||||
|
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
|
||||||
|
{
|
||||||
|
"aarch64-apple-darwin"
|
||||||
|
}
|
||||||
|
#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
|
||||||
|
{
|
||||||
|
"x86_64-pc-windows-gnu"
|
||||||
|
}
|
||||||
|
#[cfg(all(target_arch = "aarch64", target_os = "windows"))]
|
||||||
|
{
|
||||||
|
"aarch64-pc-windows-gnullvm"
|
||||||
|
}
|
||||||
|
#[cfg(not(any(
|
||||||
|
all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"),
|
||||||
|
all(target_arch = "aarch64", target_os = "linux", target_env = "gnu"),
|
||||||
|
all(target_arch = "x86_64", target_os = "macos"),
|
||||||
|
all(target_arch = "aarch64", target_os = "macos"),
|
||||||
|
all(target_arch = "x86_64", target_os = "windows"),
|
||||||
|
all(target_arch = "aarch64", target_os = "windows"),
|
||||||
|
)))]
|
||||||
|
{
|
||||||
|
"unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dist artifact filename: `{hash}-{part_id}-cd.tar.gz` or `{hash}-{part_id}-{version}.tar.gz`.
|
||||||
|
pub fn part_dist_artifact_name(hash: &str, part_id: &str, version: &str, cd: bool) -> String {
|
||||||
|
if cd {
|
||||||
|
format!("{hash}-{part_id}-cd.tar.gz")
|
||||||
|
} else {
|
||||||
|
format!("{hash}-{part_id}-{version}.tar.gz")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parts_dist_cd(mize: &mut Mize) -> bool {
|
||||||
|
mize.get_config("parts.dist.cd")
|
||||||
|
.ok()
|
||||||
|
.and_then(|d| d.value_bool().ok())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read `version` from a Cargo.toml's `[package]` table.
|
||||||
|
pub fn cargo_package_version(cargo_toml_path: &str) -> MizeResult<String> {
|
||||||
|
let contents = fs::read_to_string(cargo_toml_path)
|
||||||
|
.map_err(|e| mize_err!("could not read {cargo_toml_path}: {e}"))?;
|
||||||
|
let data = ItemData::from_toml(&contents)?;
|
||||||
|
data.get_path("package.version")?
|
||||||
|
.value_string()
|
||||||
|
.map_err(|e| e.msg(format!("no package.version in {cargo_toml_path}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a part's cargo version for dist URLs (non-cd).
|
||||||
|
pub fn resolve_part_version(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
|
||||||
|
if let Ok(v) = mize.get_config(&format!("{part_name}.version")) {
|
||||||
|
if let Ok(s) = v.value_string() {
|
||||||
|
if !s.is_empty() {
|
||||||
|
return Ok(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if part_name == "mize" {
|
||||||
|
return Ok(env!("CARGO_PKG_VERSION").to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(workspaces) = mize.get_config("parts.workspaces") {
|
||||||
|
if let Some(arr) = workspaces.cbor().as_array() {
|
||||||
|
for src in arr {
|
||||||
|
let Some(dir) = src.as_text() else { continue };
|
||||||
|
for candidate in [
|
||||||
|
format!("{dir}/{part_name}/Cargo.toml"),
|
||||||
|
format!("{dir}/mize.{part_name}/Cargo.toml"),
|
||||||
|
] {
|
||||||
|
if PathBuf::from(&candidate).exists() {
|
||||||
|
return cargo_package_version(&candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mize_src) = mize.get_config("mize.src").and_then(|d| d.value_string()) {
|
||||||
|
for candidate in [
|
||||||
|
format!("{mize_src}/parts/{part_name}/Cargo.toml"),
|
||||||
|
format!("{mize_src}/parts/mize.{part_name}/Cargo.toml"),
|
||||||
|
format!("{mize_src}/core/Cargo.toml"),
|
||||||
|
] {
|
||||||
|
if part_name != "mize" && candidate.ends_with("core/Cargo.toml") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if PathBuf::from(&candidate).exists() {
|
||||||
|
return cargo_package_version(&candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(mize_err!(
|
||||||
|
"could not resolve cargo version for part '{part_name}' (set {part_name}.version or parts.workspaces, or enable parts.dist.cd)"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_selector_base(mize: &mut Mize) -> ItemData {
|
||||||
|
// Prefer config opt; fall back to store path used by tests / older setups.
|
||||||
|
if let Ok(sel) = mize.get("self/config/selector").and_then(|i| i.as_data_full()) {
|
||||||
|
if !sel.cbor().is_null() {
|
||||||
|
return sel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mize.get("0/config/selector")
|
||||||
|
.and_then(|i| i.as_data_full())
|
||||||
|
.unwrap_or_else(|_| ItemData::new())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_part_hash(
|
pub fn get_part_hash(
|
||||||
mize: &mut Mize,
|
mize: &mut Mize,
|
||||||
name: &str,
|
name: &str,
|
||||||
options: ItemData,
|
options: ItemData,
|
||||||
) -> MizeResult<(String, String)> {
|
) -> MizeResult<(String, String)> {
|
||||||
let mut selector_data = mize.get("self/config/selector")?.as_data_full()?;
|
let mut selector_data = load_selector_base(mize);
|
||||||
|
|
||||||
|
if selector_data
|
||||||
|
.get_path("mize_version")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.value_string().ok())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
selector_data.set_path("mize_version", env!("CARGO_PKG_VERSION"))?;
|
||||||
|
}
|
||||||
|
if selector_data
|
||||||
|
.get_path("system")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.value_string().ok())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
selector_data.set_path("system", host_rust_target())?;
|
||||||
|
}
|
||||||
|
|
||||||
// handle the case, when we want a part for not the system we are running on...
|
// handle the case, when we want a part for not the system we are running on...
|
||||||
debug!("get_part_hash: name: {}", name);
|
debug!("get_part_hash: name: {}", name);
|
||||||
@@ -227,21 +373,31 @@ pub fn get_part_hash(
|
|||||||
|
|
||||||
pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
|
pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
|
||||||
let (part_hash, part_selector) = get_part_hash(mize, part_name, ItemData::new())?;
|
let (part_hash, part_selector) = get_part_hash(mize, part_name, ItemData::new())?;
|
||||||
let part_url = "https://mize.works/mize/dist/{part_hash}-{part_name}.tar.gz";
|
let cd = parts_dist_cd(mize);
|
||||||
|
let version = if cd {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
resolve_part_version(mize, part_name)?
|
||||||
|
};
|
||||||
|
let artifact = part_dist_artifact_name(&part_hash, part_name, &version, cd);
|
||||||
|
let part_url = format!("https://dist.mize.works/{artifact}");
|
||||||
|
|
||||||
let store_path = mize.get_config("store")?.value_string()?;
|
let store_path = mize.get_config("store")?.value_string()?;
|
||||||
let parts_path = format!("{store_path}/parts");
|
let parts_path = format!("{store_path}/parts");
|
||||||
let target_dir = format!("{parts_path}/{part_hash}-{part_name}");
|
let target_dir = format!("{parts_path}/{part_hash}-{part_name}");
|
||||||
|
|
||||||
|
let tmp_file_path_gz = format!("{parts_path}/{artifact}");
|
||||||
let tmp_file_path_gz = format!("{parts_path}/{part_hash}-{part_name}.tar.gz");
|
std::fs::create_dir_all(&parts_path)?;
|
||||||
|
|
||||||
let tmp_gz_file = if !PathBuf::from(tmp_file_path_gz.clone()).exists() {
|
let tmp_gz_file = if !PathBuf::from(tmp_file_path_gz.clone()).exists() {
|
||||||
let mut tmp_gz_file = File::create(&tmp_file_path_gz)?;
|
let mut tmp_gz_file = File::create(&tmp_file_path_gz)?;
|
||||||
|
|
||||||
debug!("downloading part '{}' with hash: {}", part_name, part_hash);
|
debug!(
|
||||||
|
"downloading part '{}' from {} (selector: {})",
|
||||||
|
part_name, part_url, part_selector
|
||||||
|
);
|
||||||
|
|
||||||
let response = http_req::request::get(part_url, &mut tmp_gz_file)?;
|
let response = http_req::request::get(&part_url, &mut tmp_gz_file)?;
|
||||||
|
|
||||||
let code = response.status_code();
|
let code = response.status_code();
|
||||||
if code != 200.into() {
|
if code != 200.into() {
|
||||||
@@ -252,8 +408,8 @@ pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
|
|||||||
part_selector
|
part_selector
|
||||||
)
|
)
|
||||||
.msg(format!(
|
.msg(format!(
|
||||||
"the http request to fetch the part returned a status code of: {}",
|
"the http request to fetch the part from '{}' returned a status code of: {}",
|
||||||
code
|
part_url, code
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,8 +465,11 @@ fn get_part_path(mize: &mut Mize, part_name: &str, _path: Option<String>) -> Miz
|
|||||||
//Err(mize_err!("part {part_name} not found"))
|
//Err(mize_err!("part {part_name} not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> MizeResult<()> {
|
/// Load a part `.so` for `part_name`, registering it under `at_id` (or `part_name` if None).
|
||||||
let part_path = get_part_path(mize, part_name, path)?;
|
/// Uses `get_global_mize()` so nested loads from inside another part's init work.
|
||||||
|
pub fn load_part(part_name: &str, path: Option<String>, at_id: Option<&str>) -> MizeResult<()> {
|
||||||
|
let mut mize = crate::get_global_mize()?;
|
||||||
|
let part_path = get_part_path(&mut mize, part_name, path)?;
|
||||||
|
|
||||||
let lib = unsafe { libloading::Library::new(part_path)? };
|
let lib = unsafe { libloading::Library::new(part_path)? };
|
||||||
|
|
||||||
@@ -326,32 +485,35 @@ pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> Mize
|
|||||||
|
|
||||||
let part_symbol_name = part_name.replace(".", "_");
|
let part_symbol_name = part_name.replace(".", "_");
|
||||||
|
|
||||||
let func: libloading::Symbol<
|
let func: libloading::Symbol<unsafe extern "C" fn()> =
|
||||||
unsafe extern "C" fn() -> MizeResult<()>,
|
unsafe { lib.get(format!("part_{}", part_symbol_name).as_bytes())? };
|
||||||
> = unsafe { lib.get(format!("part_{}", part_symbol_name).as_bytes())? };
|
|
||||||
|
|
||||||
let part_id = mize.clone().id_new_dots(part_name)?;
|
let eval_name = at_id.unwrap_or(part_name);
|
||||||
|
let part_id = mize.clone().id_new_dots(eval_name)?;
|
||||||
{
|
{
|
||||||
let mut opts = mize.config_opts.lock()?;
|
let mut opts = mize.config_opts.lock()?;
|
||||||
opts.entry(part_id.clone()).or_insert_with(|| ConfigOpt {
|
opts.entry(part_id.clone()).or_insert_with(|| ConfigOpt {
|
||||||
name: part_name.to_owned(),
|
name: eval_name.to_owned(),
|
||||||
val: Some(ItemData::new()),
|
val: Some(ItemData::new()),
|
||||||
fun: None,
|
fun: None,
|
||||||
deps: Vec::new(),
|
deps: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prev_eval = mize.clone().cur_eval_part()?;
|
||||||
mize.clone().set_cur_eval_part(Some(part_id))?;
|
mize.clone().set_cur_eval_part(Some(part_id))?;
|
||||||
unsafe { func()? };
|
crate::c_api::clear_err();
|
||||||
mize.clone().set_cur_eval_part(None)?;
|
unsafe { func() };
|
||||||
|
let c_err = crate::c_api::take_err();
|
||||||
let mut cur_eval_part = mize.cur_eval_part.lock()?;
|
mize.clone().set_cur_eval_part(prev_eval)?;
|
||||||
*cur_eval_part = None;
|
|
||||||
|
|
||||||
// dropping the lib, would (i suspect) free all memory, of the library's code, which would
|
// dropping the lib, would (i suspect) free all memory, of the library's code, which would
|
||||||
// make the part's vtable point into empty memory
|
// make the part's vtable point into empty memory
|
||||||
std::mem::forget(lib);
|
std::mem::forget(lib);
|
||||||
|
|
||||||
|
if let Some(err) = c_err {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
docs/general-agent-guide.md
Normal file
57
docs/general-agent-guide.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
## Require clarification and plan approval before making architectural code changes
|
||||||
|
|
||||||
|
Before making any code changes which could be considered to influence the architekture of the application, you must follow this two-step process:
|
||||||
|
|
||||||
|
### Step 1: Ask Clarifying Questions
|
||||||
|
|
||||||
|
- Always ask at least one clarifying question about the user's request
|
||||||
|
- Understand the full scope and context of what they're asking for
|
||||||
|
- Clarify any ambiguous requirements or edge cases
|
||||||
|
- Ask about preferred approaches if multiple solutions exist
|
||||||
|
- Confirm the expected behavior and user experience
|
||||||
|
- Ask the clarifying questions by numbering them, so the user can number their replies.
|
||||||
|
|
||||||
|
### Step 2: Present Implementation Plan
|
||||||
|
|
||||||
|
- After receiving clarification, present a detailed implementation plan
|
||||||
|
- Break down the work into specific, actionable steps
|
||||||
|
- Identify which files will be created, modified, or deleted
|
||||||
|
- Explain the technical approach and any architectural decisions
|
||||||
|
- Highlight any potential risks, trade-offs, or dependencies
|
||||||
|
- Estimate the complexity and scope of changes
|
||||||
|
- **Wait for explicit user approval** before proceeding with any code changes
|
||||||
|
|
||||||
|
### What are considered architectural changes
|
||||||
|
|
||||||
|
**Architectural** (clarify → plan → wait for approval), for example:
|
||||||
|
- New parts, or new cross-part `method` / `opt` surfaces
|
||||||
|
- Wiring one part into another (e.g. UI shell calling `auth.client.login`, soft-notify via `has_part`)
|
||||||
|
- Changes to core loading / `has_part` / part-system semantics
|
||||||
|
- Splitting or merging capabilities across part boundaries
|
||||||
|
|
||||||
|
**Not architectural** (implement directly when the intent is clear), for example:
|
||||||
|
- Layout / CSS / copy tweaks in an existing screen
|
||||||
|
- Adding another display line or button that only uses existing local state or already-exposed methods
|
||||||
|
- Bugfixes that don’t change the part API (e.g. `document::Style` re-render warnings)
|
||||||
|
|
||||||
|
### Approval Requirements
|
||||||
|
|
||||||
|
- User must explicitly approve the plan with words like "yes", "approved", "proceed", "go ahead", or similar
|
||||||
|
- If the user suggests modifications to the plan, incorporate them and seek re-approval
|
||||||
|
- Do not assume silence or ambiguous responses mean approval
|
||||||
|
|
||||||
|
### Exceptions
|
||||||
|
|
||||||
|
- This process may be skipped only for trivial changes like fixing obvious typos or formatting
|
||||||
|
- When in doubt, always follow the full process rather than assuming an exception applies
|
||||||
|
|
||||||
|
### Example Workflow
|
||||||
|
|
||||||
|
1. User: "Add a login form to the app"
|
||||||
|
2. Assistant: "I'd like to clarify a few things about the login form: [questions]"
|
||||||
|
3. User: [provides answers]
|
||||||
|
4. Assistant: "Based on your requirements, here's my implementation plan: [detailed plan]. Does this approach look good to you?"
|
||||||
|
5. User: "Yes, that looks good"
|
||||||
|
6. Assistant: [writes plan to file].
|
||||||
|
7. Assistant: [proceeds with implementation].
|
||||||
@@ -1,15 +1,5 @@
|
|||||||
# Mize Part System (LLM generated guide)
|
# 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
18
docs/plans/2026-11-03-dist-publish.md
Normal file
18
docs/plans/2026-11-03-dist-publish.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Plan: dist publish + dist.mize.works (approved)
|
||||||
|
|
||||||
|
## Config
|
||||||
|
- `parts.workspaces`: list of dirs; each subdir is a part to build/upload
|
||||||
|
- `parts.dist.cd`: bool; when true artifact uses `-cd` instead of `-{version}`
|
||||||
|
|
||||||
|
## Publish (`mize.dev`)
|
||||||
|
- After build: pack each part `.so` under `lib/`, special part `mize` gets `libmize.so` + `bin/mize`
|
||||||
|
- Hash via `get_part_hash` with modName, system (target), mize_version
|
||||||
|
- Filename: `{hash}-{id}-{version}.tar.gz` or `{hash}-{id}-cd.tar.gz`
|
||||||
|
- rsync to `ocib:host/mize-dist/`
|
||||||
|
|
||||||
|
## fetch_part
|
||||||
|
- Base URL `https://dist.mize.works/`
|
||||||
|
- Same naming; ensure mize_version in selector
|
||||||
|
|
||||||
|
## ocib
|
||||||
|
- `~/host/docker-compose.yaml`: Traefik + kugland/darkhttpd serving `mize-dist` for `dist.mize.works`
|
||||||
14
docs/plans/2026-11-04-load-part-at-instances.md
Normal file
14
docs/plans/2026-11-04-load-part-at-instances.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# load_part_at instances (priv.spacetime / ppc.spacetime)
|
||||||
|
|
||||||
|
Approved 2026-11-04.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Instantiate the same `spacetime` `.so` under different part ids (`priv.spacetime`, `ppc.spacetime`), each with its own config namespace. Dependents use `Spacetime::get("priv.spacetime")` etc.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
1. Platform `load_part(part_name, path, at_id)` via `get_global_mize()`; save/restore `cur_eval_part`.
|
||||||
|
2. Rename `Mize::load_part_at` → `load_part_from_path`.
|
||||||
|
3. `config::load_part_at(at_id, part_name)` — no-op in generate mode.
|
||||||
|
4. Codegen: special SpacetimeDB client gen when `gen` contains `"spacetime"`, module at `{cwd}/spacetime` if present.
|
||||||
|
5. `sci`: `gen += spacetime`, `load_part_at("priv.spacetime", "spacetime")`.
|
||||||
|
6. New `mize/parts/ppc`: loads `ppc.spacetime`.
|
||||||
0
docs/software-factory.md
Normal file
0
docs/software-factory.md
Normal file
@@ -202,6 +202,18 @@ pub extern "C" fn part_auth_client() -> MizeResult<()> {
|
|||||||
Ok(ItemData::new())
|
Ok(ItemData::new())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
method("get_id_token", "() -> String", |_args| {
|
||||||
|
let data = get_config("self.secrets")?;
|
||||||
|
let secrets = match data.deserialize::<Option<ClientSecrets>>() {
|
||||||
|
Ok(Some(secrets)) => secrets,
|
||||||
|
Ok(None) | Err(_) => {
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
load_secrets(&mut mize).map_err(|_| mize_err!("not logged in"))?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(secrets.id_token.into_item_data())
|
||||||
|
});
|
||||||
|
|
||||||
let mut mize = get_global_mize()?;
|
let mut mize = get_global_mize()?;
|
||||||
|
|
||||||
// start refresh tread if configured
|
// start refresh tread if configured
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
cargo-features = ["profile-rustflags"]
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "mize-part-dev"
|
name = "mize-part-mize-dev"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,13 @@ fn generate_part_struct(out: &mut String, meta: &PartMetadata) {
|
|||||||
let _ = writeln!(out);
|
let _ = writeln!(out);
|
||||||
|
|
||||||
// part methods (skip ones that belong to include_native)
|
// part methods (skip ones that belong to include_native)
|
||||||
for m in meta.methods.iter().chain(meta.effects.iter()).chain(meta.threads.iter()) {
|
for m in meta
|
||||||
|
.methods
|
||||||
|
.iter()
|
||||||
|
.chain(meta.effects.iter())
|
||||||
|
.chain(meta.applies.iter())
|
||||||
|
.chain(meta.threads.iter())
|
||||||
|
{
|
||||||
if native_fns.contains(&m.name) {
|
if native_fns.contains(&m.name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -466,7 +472,12 @@ pub fn extract_mize_dependency(cargo_toml: &str) -> Result<String, String> {
|
|||||||
///
|
///
|
||||||
/// `mize_dep` is the full assignment line copied from the part Cargo.toml,
|
/// `mize_dep` is the full assignment line copied from the part Cargo.toml,
|
||||||
/// e.g. `mize = { workspace = true }` or `mize = { path = "../../core" }`.
|
/// e.g. `mize = { workspace = true }` or `mize = { path = "../../core" }`.
|
||||||
pub fn generate_cargo_toml(package_name: &str, mize_dep: &str) -> String {
|
pub fn generate_cargo_toml(package_name: &str, mize_dep: &str, spacetime_sdk: bool) -> String {
|
||||||
|
let spacetime_dep = if spacetime_sdk {
|
||||||
|
"spacetimedb-sdk = \"2.6.0\"\n"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
format!(
|
format!(
|
||||||
r#"[package]
|
r#"[package]
|
||||||
name = "{package_name}"
|
name = "{package_name}"
|
||||||
@@ -479,9 +490,10 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
{mize_dep}
|
{mize_dep}
|
||||||
"#,
|
{spacetime_dep}"#,
|
||||||
package_name = package_name,
|
package_name = package_name,
|
||||||
lib_name = package_name.replace('-', "_"),
|
lib_name = package_name.replace('-', "_"),
|
||||||
mize_dep = mize_dep.trim(),
|
mize_dep = mize_dep.trim(),
|
||||||
|
spacetime_dep = spacetime_dep,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
use std::fs::{create_dir_all, write};
|
use std::fs::{create_dir_all, write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use mize::prelude::*;
|
use mize::prelude::*;
|
||||||
use cmd_lib::run_cmd as sh;
|
use cmd_lib::run_cmd as sh;
|
||||||
use mize::tracing::info;
|
use mize::tracing::info;
|
||||||
use mize::tracing::debug;
|
use mize::tracing::debug;
|
||||||
|
use mize::platform::os::{
|
||||||
|
cargo_package_version, get_part_hash, part_dist_artifact_name, parts_dist_cd,
|
||||||
|
};
|
||||||
|
|
||||||
// a static version of what would be generated.
|
// a static version of what would be generated.
|
||||||
// as we need mize.dev to generate anything.
|
// as we need mize.dev to generate anything.
|
||||||
@@ -10,8 +14,6 @@ mod generated;
|
|||||||
mod codegen;
|
mod codegen;
|
||||||
use generated::*;
|
use generated::*;
|
||||||
|
|
||||||
// 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)?;
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn part_dev() -> MizeResult<()> {
|
extern "C" fn part_dev() -> MizeResult<()> {
|
||||||
let cli = generated::CliPart::get("cli")?;
|
let cli = generated::CliPart::get("cli")?;
|
||||||
@@ -26,20 +28,245 @@ extern "C" fn part_dev() -> MizeResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PartToPublish {
|
||||||
|
/// Runtime part id (e.g. `dev`, `auth.client`, `mize`)
|
||||||
|
id: String,
|
||||||
|
/// Directory containing Cargo.toml (None for the special `mize` core part)
|
||||||
|
dir: Option<PathBuf>,
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dylib_ext(target: &str) -> &'static str {
|
||||||
|
if target.contains("windows") {
|
||||||
|
"dll"
|
||||||
|
} else if target.contains("apple") || target.contains("darwin") {
|
||||||
|
"dylib"
|
||||||
|
} else {
|
||||||
|
"so"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_lib_filename(part_id: &str, target: &str) -> String {
|
||||||
|
let sym = part_id.replace('.', "_");
|
||||||
|
let ext = dylib_ext(target);
|
||||||
|
if target.contains("windows") {
|
||||||
|
format!("mize_part_{sym}.{ext}")
|
||||||
|
} else {
|
||||||
|
format!("libmize_part_{sym}.{ext}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mize_lib_filename(target: &str) -> String {
|
||||||
|
let ext = dylib_ext(target);
|
||||||
|
if target.contains("windows") {
|
||||||
|
format!("mize.{ext}")
|
||||||
|
} else {
|
||||||
|
format!("libmize.{ext}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mize_bin_filename(target: &str) -> String {
|
||||||
|
if target.contains("windows") {
|
||||||
|
"mize.exe".to_owned()
|
||||||
|
} else {
|
||||||
|
"mize".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_dir(mize_src: &str, target: &str) -> PathBuf {
|
||||||
|
PathBuf::from(mize_src)
|
||||||
|
.join("target")
|
||||||
|
.join(target)
|
||||||
|
.join("release")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_part_id(dir: &Path) -> MizeResult<String> {
|
||||||
|
let part_file = dir.join("part");
|
||||||
|
if part_file.exists() {
|
||||||
|
let toml = std::fs::read_to_string(&part_file)
|
||||||
|
.map_err(|e| mize_err!("could not read {}: {e}", part_file.display()))?;
|
||||||
|
let data = ItemData::from_toml(&toml)?;
|
||||||
|
if let Ok(id) = data.get_path("id").and_then(|v| v.value_string()) {
|
||||||
|
if !id.is_empty() {
|
||||||
|
return Ok(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cargo_path = dir.join("Cargo.toml");
|
||||||
|
let cargo = std::fs::read_to_string(&cargo_path)
|
||||||
|
.map_err(|e| mize_err!("could not read {}: {e}", cargo_path.display()))?;
|
||||||
|
let data = ItemData::from_toml(&cargo)?;
|
||||||
|
if let Ok(lib_name) = data.get_path("lib.name").and_then(|v| v.value_string()) {
|
||||||
|
if let Some(rest) = lib_name.strip_prefix("mize_part_") {
|
||||||
|
return Ok(rest.replace('_', "."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir.file_name()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.ok_or_else(|| mize_err!("could not resolve part id for {}", dir.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_parts_to_publish(mize: &mut Mize, mize_src: &str) -> MizeResult<Vec<PartToPublish>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
|
||||||
|
// Special core part "mize"
|
||||||
|
let mize_version = cargo_package_version(&format!("{mize_src}/core/Cargo.toml"))?;
|
||||||
|
out.push(PartToPublish {
|
||||||
|
id: "mize".to_owned(),
|
||||||
|
dir: None,
|
||||||
|
version: mize_version.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let workspaces = match mize.get_config("parts.workspaces") {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(_) => {
|
||||||
|
info!("parts.workspaces not set; only publishing special part 'mize'");
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let arr = workspaces
|
||||||
|
.cbor()
|
||||||
|
.as_array()
|
||||||
|
.ok_or_else(|| mize_err!("parts.workspaces must be an array of directory paths"))?;
|
||||||
|
|
||||||
|
for src in arr {
|
||||||
|
let dir_str = src
|
||||||
|
.as_text()
|
||||||
|
.ok_or_else(|| mize_err!("parts.workspaces entries must be strings"))?;
|
||||||
|
let dir = PathBuf::from(dir_str);
|
||||||
|
if !dir.is_dir() {
|
||||||
|
info!("parts.workspaces entry is not a directory, skipping: {dir_str}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let entries = std::fs::read_dir(&dir)
|
||||||
|
.map_err(|e| mize_err!("could not read parts.workspaces dir {dir_str}: {e}"))?;
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(|e| mize_err!("read_dir error in {dir_str}: {e}"))?;
|
||||||
|
let path = entry.path();
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let cargo = path.join("Cargo.toml");
|
||||||
|
if !cargo.exists() {
|
||||||
|
debug!("skipping {} (no Cargo.toml)", path.display());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let id = resolve_part_id(&path)?;
|
||||||
|
let version = cargo_package_version(cargo.to_str().unwrap())?;
|
||||||
|
out.push(PartToPublish {
|
||||||
|
id,
|
||||||
|
dir: Some(path),
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pack_and_upload_part(
|
||||||
|
mize: &mut Mize,
|
||||||
|
mize_src: &str,
|
||||||
|
target: &str,
|
||||||
|
part: &PartToPublish,
|
||||||
|
mize_version: &str,
|
||||||
|
) -> MizeResult<()> {
|
||||||
|
let rel = release_dir(mize_src, target);
|
||||||
|
let tmp = std::env::var("TMP").unwrap_or_else(|_| "/tmp".to_owned());
|
||||||
|
let staging = PathBuf::from(&tmp).join(format!("mize-dist-{}-{}", part.id, target));
|
||||||
|
if staging.exists() {
|
||||||
|
std::fs::remove_dir_all(&staging)
|
||||||
|
.map_err(|e| mize_err!("could not clear staging {}: {e}", staging.display()))?;
|
||||||
|
}
|
||||||
|
let lib_dir = staging.join("lib");
|
||||||
|
create_dir_all(&lib_dir)
|
||||||
|
.map_err(|e| mize_err!("could not create {}: {e}", lib_dir.display()))?;
|
||||||
|
|
||||||
|
if part.id == "mize" {
|
||||||
|
let lib_name = mize_lib_filename(target);
|
||||||
|
let bin_name = mize_bin_filename(target);
|
||||||
|
let lib_src = rel.join(&lib_name);
|
||||||
|
let bin_src = rel.join(&bin_name);
|
||||||
|
if !lib_src.exists() {
|
||||||
|
return Err(mize_err!("missing built artifact {}", lib_src.display()));
|
||||||
|
}
|
||||||
|
if !bin_src.exists() {
|
||||||
|
return Err(mize_err!("missing built artifact {}", bin_src.display()));
|
||||||
|
}
|
||||||
|
std::fs::copy(&lib_src, lib_dir.join(&lib_name))
|
||||||
|
.map_err(|e| mize_err!("copy {}: {e}", lib_src.display()))?;
|
||||||
|
let bin_dir = staging.join("bin");
|
||||||
|
create_dir_all(&bin_dir)?;
|
||||||
|
std::fs::copy(&bin_src, bin_dir.join(&bin_name))
|
||||||
|
.map_err(|e| mize_err!("copy {}: {e}", bin_src.display()))?;
|
||||||
|
} else {
|
||||||
|
let lib_name = part_lib_filename(&part.id, target);
|
||||||
|
let lib_src = rel.join(&lib_name);
|
||||||
|
if !lib_src.exists() {
|
||||||
|
info!(
|
||||||
|
"skipping part '{}': missing {} (not built for this target?)",
|
||||||
|
part.id,
|
||||||
|
lib_src.display()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// Archive always uses the fetch_part layout: lib/libmize_part_<sym>.so
|
||||||
|
// For non-linux targets keep the native filename inside lib/.
|
||||||
|
let dest_name = if dylib_ext(target) == "so" {
|
||||||
|
format!("libmize_part_{}.so", part.id.replace('.', "_"))
|
||||||
|
} else {
|
||||||
|
lib_name.clone()
|
||||||
|
};
|
||||||
|
std::fs::copy(&lib_src, lib_dir.join(&dest_name))
|
||||||
|
.map_err(|e| mize_err!("copy {}: {e}", lib_src.display()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hash_opts = ItemData::new();
|
||||||
|
hash_opts.set_path("system", target)?;
|
||||||
|
hash_opts.set_path("mize_version", mize_version)?;
|
||||||
|
let (hash, selector) = get_part_hash(mize, &part.id, hash_opts)?;
|
||||||
|
debug!("part {} selector: {}", part.id, selector);
|
||||||
|
|
||||||
|
let cd = parts_dist_cd(mize);
|
||||||
|
let artifact = part_dist_artifact_name(&hash, &part.id, &part.version, cd);
|
||||||
|
let archive_path = PathBuf::from(&tmp).join(&artifact);
|
||||||
|
|
||||||
|
let staging_str = staging.to_string_lossy().to_string();
|
||||||
|
let archive_str = archive_path.to_string_lossy().to_string();
|
||||||
|
sh!(tar -C $staging_str -czf $archive_str .)?;
|
||||||
|
|
||||||
|
info!("uploading {artifact} -> ocib:host/mize-dist/");
|
||||||
|
sh!(rsync -av $archive_str ocib:host/mize-dist/)?;
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&staging);
|
||||||
|
let _ = std::fs::remove_file(&archive_path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn publish_mize(_ : ArgMatches) -> MizeResult<()> {
|
pub fn publish_mize(_ : ArgMatches) -> MizeResult<()> {
|
||||||
let mut mize = get_global_mize()?;
|
let mut mize = get_global_mize()?;
|
||||||
let mize_src = mize.get_config("mize.src")?.value_string()?;
|
let mize_src = mize.get_config("mize.src")?.value_string()?;
|
||||||
let cargo_dir = std::env::home_dir().unwrap().join(".cargo");
|
let cargo_dir = std::env::home_dir().unwrap().join(".cargo");
|
||||||
|
let mize_version = cargo_package_version(&format!("{mize_src}/core/Cargo.toml"))?;
|
||||||
|
let parts = collect_parts_to_publish(&mut mize, &mize_src)?;
|
||||||
|
info!(
|
||||||
|
"publishing {} part(s) (mize_version={mize_version}, cd={})",
|
||||||
|
parts.len(),
|
||||||
|
parts_dist_cd(&mut mize)
|
||||||
|
);
|
||||||
|
|
||||||
let targets = vec![
|
let targets = vec![
|
||||||
"x86_64-pc-windows-gnu",
|
|
||||||
"aarch64-pc-windows-gnullvm",
|
|
||||||
|
|
||||||
"x86_64-unknown-linux-gnu",
|
"x86_64-unknown-linux-gnu",
|
||||||
"aarch64-unknown-linux-gnu",
|
"aarch64-unknown-linux-gnu",
|
||||||
"armv7-unknown-linux-gnueabihf",
|
"armv7-unknown-linux-gnueabihf",
|
||||||
"arm-unknown-linux-gnueabi",
|
"arm-unknown-linux-gnueabi",
|
||||||
|
|
||||||
|
"x86_64-pc-windows-gnu",
|
||||||
|
"aarch64-pc-windows-gnullvm",
|
||||||
|
|
||||||
"aarch64-apple-darwin",
|
"aarch64-apple-darwin",
|
||||||
"x86_64-apple-darwin",
|
"x86_64-apple-darwin",
|
||||||
];
|
];
|
||||||
@@ -50,7 +277,7 @@ pub fn publish_mize(_ : ArgMatches) -> MizeResult<()> {
|
|||||||
"x86_64-apple-darwin" => "darwin",
|
"x86_64-apple-darwin" => "darwin",
|
||||||
_ => "base",
|
_ => "base",
|
||||||
};
|
};
|
||||||
let tmp = std::env::var("TMP").unwrap();
|
let tmp = std::env::var("TMP").unwrap_or_else(|_| "/tmp".to_owned());
|
||||||
let mut build_config = ItemData::new();
|
let mut build_config = ItemData::new();
|
||||||
build_config.set_path("namespace", "mize.buildtime.ns")?;
|
build_config.set_path("namespace", "mize.buildtime.ns")?;
|
||||||
std::fs::create_dir_all(&tmp)?;
|
std::fs::create_dir_all(&tmp)?;
|
||||||
@@ -58,10 +285,19 @@ pub fn publish_mize(_ : ArgMatches) -> MizeResult<()> {
|
|||||||
std::fs::write(format!("{tmp}/mize-part-dev-build-config.toml"), build_config.to_json()?)?;
|
std::fs::write(format!("{tmp}/mize-part-dev-build-config.toml"), build_config.to_json()?)?;
|
||||||
sh!(docker build -t mize-build-$docker_image $mize_src/parts/mize.dev/docker/$docker_image)?;
|
sh!(docker build -t mize-build-$docker_image $mize_src/parts/mize.dev/docker/$docker_image)?;
|
||||||
sh!(echo running in container: docker run -it --rm -v "$tmp/mize-part-dev-build-config.toml:/build-config.toml" -e MIZE_BUILD_CONFIG=/build-config.toml -v $cargo_dir/registry:/usr/local/cargo/registry -v $cargo_dir/git:/usr/local/cargo/git -v $mize_src:/src -v $mize_src/target:/src/target mize-build-$docker_image cargo build --target $target_name --release --workspace)?;
|
sh!(echo running in container: docker run -it --rm -v "$tmp/mize-part-dev-build-config.toml:/build-config.toml" -e MIZE_BUILD_CONFIG=/build-config.toml -v $cargo_dir/registry:/usr/local/cargo/registry -v $cargo_dir/git:/usr/local/cargo/git -v $mize_src:/src -v $mize_src/target:/src/target mize-build-$docker_image cargo build --target $target_name --release --workspace)?;
|
||||||
let res = sh!(docker run -it --rm -v "$tmp/mize-part-dev-build-config.toml:/build-config.toml" -e MIZE_BUILD_CONFIG=/build-config.toml -v $cargo_dir/registry:/usr/local/cargo/registry -v $cargo_dir/git:/usr/local/cargo/git -v $mize_src:/src -v $mize_src/target:/src/target mize-build-$docker_image cargo build --target $target_name --release);
|
let res = sh!(docker run -it --rm -v "$tmp/mize-part-dev-build-config.toml:/build-config.toml" -e MIZE_BUILD_CONFIG=/build-config.toml -v $cargo_dir/registry:/usr/local/cargo/registry -v $cargo_dir/git:/usr/local/cargo/git -v $mize_src:/src -v $mize_src/target:/src/target mize-build-$docker_image cargo build --target $target_name --release --workspace);
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
info!("build for {target_name} failed...");
|
info!("build for {target_name} failed...");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for part in &parts {
|
||||||
|
if let Err(e) = pack_and_upload_part(&mut mize, &mize_src, target_name, part, &mize_version) {
|
||||||
|
info!("pack/upload for part '{}' target '{target_name}' failed", part.id);
|
||||||
|
e.log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,12 +342,16 @@ pub fn gen_bindings(_ : ArgMatches) -> MizeResult<()> {
|
|||||||
gen_parts.push(s.to_owned());
|
gen_parts.push(s.to_owned());
|
||||||
}
|
}
|
||||||
debug!("parts to generate bindings for: {:?}", gen_parts);
|
debug!("parts to generate bindings for: {:?}", gen_parts);
|
||||||
|
let gen_spacetime = gen_parts.iter().any(|p| p == "spacetime");
|
||||||
|
let spacetime_module_dir = cwd.join("spacetime");
|
||||||
|
let gen_spacetime_sdk = gen_spacetime && spacetime_module_dir.is_dir();
|
||||||
|
|
||||||
// 3. enable generate mode and clear any old metadata
|
// 3. enable generate mode and clear any old metadata
|
||||||
mize.set_generate_mode(true)?;
|
mize.set_generate_mode(true)?;
|
||||||
mize.clear_metadata()?;
|
mize.clear_metadata()?;
|
||||||
|
|
||||||
// 4. load each part — in generate mode their part_smth funcs only record metadata
|
// 4. load each part — in generate mode their part_smth funcs only record metadata
|
||||||
|
// (`"spacetime"` still produces normal SpacetimePart bindings; SDK client codegen is extra)
|
||||||
for p in &gen_parts {
|
for p in &gen_parts {
|
||||||
info!("loading part in generate mode: {p}");
|
info!("loading part in generate mode: {p}");
|
||||||
mize.load_part(p)?;
|
mize.load_part(p)?;
|
||||||
@@ -136,11 +376,36 @@ pub fn gen_bindings(_ : ArgMatches) -> MizeResult<()> {
|
|||||||
let mize_dep = codegen::extract_mize_dependency(&part_cargo)
|
let mize_dep = codegen::extract_mize_dependency(&part_cargo)
|
||||||
.map_err(|e| mize_err!("{e}"))?;
|
.map_err(|e| mize_err!("{e}"))?;
|
||||||
debug!("using mize dependency from part Cargo.toml: {mize_dep}");
|
debug!("using mize dependency from part Cargo.toml: {mize_dep}");
|
||||||
let cargo_toml = codegen::generate_cargo_toml(&package_name, &mize_dep);
|
let cargo_toml = codegen::generate_cargo_toml(&package_name, &mize_dep, gen_spacetime_sdk);
|
||||||
write(gen_dir.join("Cargo.toml"), cargo_toml)
|
write(gen_dir.join("Cargo.toml"), cargo_toml)
|
||||||
.map_err(|e| mize_err!("could not write generated/Cargo.toml: {e}"))?;
|
.map_err(|e| mize_err!("could not write generated/Cargo.toml: {e}"))?;
|
||||||
|
|
||||||
let lib_rs = codegen::generate_lib_rs(&all_metadata);
|
let mut lib_rs = codegen::generate_lib_rs(&all_metadata);
|
||||||
|
|
||||||
|
// 8. if gen includes "spacetime" and {cwd}/spacetime exists, emit SpacetimeDB rust client bindings
|
||||||
|
if gen_spacetime_sdk {
|
||||||
|
let module_path = spacetime_module_dir.to_string_lossy().to_string();
|
||||||
|
let spacetime_out = src_dir.join("spacetime");
|
||||||
|
create_dir_all(&spacetime_out)
|
||||||
|
.map_err(|e| mize_err!("could not create {}: {e}", spacetime_out.display()))?;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"running spacetime generate -p {} -o {}",
|
||||||
|
module_path,
|
||||||
|
spacetime_out.display()
|
||||||
|
);
|
||||||
|
let spacetime_out_str = spacetime_out.to_string_lossy().to_string();
|
||||||
|
sh!(
|
||||||
|
RUST_LOG=error RUST_BACKTRACE=0 spacetime generate
|
||||||
|
--build-options="--lint-dir="
|
||||||
|
-p $module_path
|
||||||
|
-o $spacetime_out_str
|
||||||
|
--lang rust
|
||||||
|
)?;
|
||||||
|
|
||||||
|
lib_rs.push_str("\npub mod spacetime;\npub use spacetime::*;\n");
|
||||||
|
}
|
||||||
|
|
||||||
write(src_dir.join("lib.rs"), lib_rs)
|
write(src_dir.join("lib.rs"), lib_rs)
|
||||||
.map_err(|e| mize_err!("could not write generated/src/lib.rs: {e}"))?;
|
.map_err(|e| mize_err!("could not write generated/src/lib.rs: {e}"))?;
|
||||||
|
|
||||||
|
|||||||
19
parts/mos.bash_config_cache/Cargo.toml
Normal file
19
parts/mos.bash_config_cache/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "mize-part-mos-bash-config-cache"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mize_part_mos_bash_config_cache"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { workspace = true }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
rustflags = ["-C", "prefer-dynamic"]
|
||||||
1
parts/mos.bash_config_cache/part
Normal file
1
parts/mos.bash_config_cache/part
Normal file
@@ -0,0 +1 @@
|
|||||||
|
id = "mos.bash_config_cache"
|
||||||
89
parts/mos.bash_config_cache/src/lib.rs
Normal file
89
parts/mos.bash_config_cache/src/lib.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::fs::{create_dir_all, write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use mize::prelude::*;
|
||||||
|
|
||||||
|
fn bash_double_quote(s: &str) -> String {
|
||||||
|
let mut out = String::with_capacity(s.len() + 2);
|
||||||
|
out.push('"');
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'\\' | '"' | '`' => {
|
||||||
|
out.push('\\');
|
||||||
|
out.push(c);
|
||||||
|
}
|
||||||
|
'\n' => out.push_str("\\n"),
|
||||||
|
// leave `$` unescaped so values like $CONFIG_DIR expand when sourced
|
||||||
|
_ => out.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push('"');
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opt_value_for_bash(mize: &mut Mize, opt_id: &str) -> MizeResult<String> {
|
||||||
|
println!("hiiii");
|
||||||
|
let val = mize.get_config(opt_id)?;
|
||||||
|
if let Some(arr) = val.cbor().as_array() {
|
||||||
|
println!("as arr!!");
|
||||||
|
let mut quoted = Vec::with_capacity(arr.len());
|
||||||
|
for entry in arr {
|
||||||
|
let s = entry.as_text().ok_or_else(|| {
|
||||||
|
mize_err!("mos.bash_config_cache: list opt '{opt_id}' has a non-string entry")
|
||||||
|
})?;
|
||||||
|
quoted.push(bash_double_quote(s));
|
||||||
|
}
|
||||||
|
return Ok(quoted.join(" "));
|
||||||
|
}
|
||||||
|
val.value_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_cache(_args: ItemData) -> MizeResult<ItemData> {
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
let store = mize.get_config("store")?.value_string()?;
|
||||||
|
let opts_data = mize.get_config("mos.bash_config_cache.opts")?;
|
||||||
|
|
||||||
|
let arr = opts_data
|
||||||
|
.cbor()
|
||||||
|
.as_array()
|
||||||
|
.ok_or_else(|| mize_err!("mos.bash_config_cache.opts is not a list"))?;
|
||||||
|
|
||||||
|
let mut body = String::from("# generated by mos.bash_config_cache.write_cache — do not edit\n");
|
||||||
|
body.push_str("declare -A myconfig\n");
|
||||||
|
|
||||||
|
for entry in arr {
|
||||||
|
let opt_id = entry
|
||||||
|
.as_text()
|
||||||
|
.ok_or_else(|| mize_err!("mos.bash_config_cache.opts entry is not a string"))?;
|
||||||
|
let val = opt_value_for_bash(&mut mize, opt_id)?;
|
||||||
|
body.push_str(&format!(
|
||||||
|
"myconfig[{}]={}\n",
|
||||||
|
bash_double_quote(opt_id),
|
||||||
|
bash_double_quote(&val)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_dir = PathBuf::from(&store).join("misc");
|
||||||
|
create_dir_all(&out_dir)?;
|
||||||
|
let out_path = out_dir.join("mos.bash_config_cache");
|
||||||
|
write(&out_path, body)?;
|
||||||
|
Ok(ItemData::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn part_mos_bash_config_cache() -> MizeResult<()> {
|
||||||
|
// toml may already have populated opts; opt() inserts empty and would clobber it
|
||||||
|
let preexisting = get_global_mize()?
|
||||||
|
.get_config("mos.bash_config_cache.opts")
|
||||||
|
.ok();
|
||||||
|
let opts_id = opt("opts");
|
||||||
|
if let Some(val) = preexisting {
|
||||||
|
opts_id.default_val(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
apply("write_cache", write_cache);
|
||||||
|
|
||||||
|
// until apply hooks run at startup automatically
|
||||||
|
write_cache(ItemData::new())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
29
parts/ppc.server/Cargo.toml
Normal file
29
parts/ppc.server/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "mize-part-ppc-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mize_part_ppc_server"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { workspace = true }
|
||||||
|
axum = "0.8.9"
|
||||||
|
axum-oidc = "=1.0.0-dev-1"
|
||||||
|
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], default-features = false }
|
||||||
|
tower = "0.5.3"
|
||||||
|
tower-sessions = "0.15.0"
|
||||||
|
tower-http = { version = "0.7.1", features = ["cors", "trace"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "net", "macros"] }
|
||||||
|
tracing = "0.1"
|
||||||
|
dioxus = { version = "0.7", default-features = false, features = ["ssr", "macro", "html", "signals"] }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
rustflags = ["-C", "prefer-dynamic"]
|
||||||
176
parts/ppc.server/src/lib.rs
Normal file
176
parts/ppc.server/src/lib.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
mod pages;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
Json, Router,
|
||||||
|
error_handling::HandleErrorLayer,
|
||||||
|
http::Uri,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{any, get},
|
||||||
|
};
|
||||||
|
use axum_oidc::handle_oidc_redirect;
|
||||||
|
use axum_oidc::{
|
||||||
|
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
|
||||||
|
OidcRpInitiatedLogout, error::MiddlewareError,
|
||||||
|
};
|
||||||
|
use mize::prelude::*;
|
||||||
|
use openidconnect::Scope;
|
||||||
|
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
use tower::ServiceBuilder;
|
||||||
|
use tower_sessions::{
|
||||||
|
Expiry, MemoryStore, SessionManagerLayer,
|
||||||
|
cookie::{SameSite, time::Duration},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn ensure_default(rel: &str, default: ItemData) -> MizeResult<()> {
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
let cur = mize
|
||||||
|
.cur_eval_part()?
|
||||||
|
.ok_or_else(|| mize_err!("no cur_eval_part"))?;
|
||||||
|
let full = format!("{}.{}", cur.dot(), rel);
|
||||||
|
if mize.get_config(&full).is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let segs: Vec<&str> = rel.split('.').filter(|s| !s.is_empty()).collect();
|
||||||
|
let last = *segs.last().ok_or_else(|| mize_err!("empty opt path"))?;
|
||||||
|
let mut parent = cur.clone();
|
||||||
|
for seg in &segs[..segs.len() - 1] {
|
||||||
|
parent = parent.join(seg)?;
|
||||||
|
}
|
||||||
|
let original = Some(cur);
|
||||||
|
mize.set_cur_eval_part(Some(parent))?;
|
||||||
|
opt(last).default_val(default);
|
||||||
|
mize.set_cur_eval_part(original)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn part_ppc_server() -> MizeResult<()> {
|
||||||
|
ensure_default("url", "http://localhost:3000".into_item_data())?;
|
||||||
|
ensure_default("bind", "0.0.0.0:8010".into_item_data())?;
|
||||||
|
|
||||||
|
thread::spawn(|| {
|
||||||
|
let rt = match Runtime::new() {
|
||||||
|
Ok(rt) => rt,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("ppc.server: failed to create runtime: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(e) = rt.block_on(start_server()) {
|
||||||
|
e.log();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_server() -> MizeResult<()> {
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
|
||||||
|
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("ppc.server.url")?.value_string()?;
|
||||||
|
let bind = mize.get_config("ppc.server.bind")?.value_string()?;
|
||||||
|
let redirect_url = format!("{url}/oidc");
|
||||||
|
|
||||||
|
let session_store = MemoryStore::default();
|
||||||
|
let session_layer = SessionManagerLayer::new(session_store)
|
||||||
|
.with_secure(false)
|
||||||
|
.with_same_site(SameSite::Lax)
|
||||||
|
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
|
||||||
|
|
||||||
|
let oidc_login_service = ServiceBuilder::new()
|
||||||
|
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||||
|
dbg!(&e);
|
||||||
|
e.into_response()
|
||||||
|
}))
|
||||||
|
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
|
||||||
|
|
||||||
|
let oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
|
||||||
|
.with_default_http_client()
|
||||||
|
.with_redirect_url(Uri::from_str(redirect_url.as_str())?)
|
||||||
|
.with_client_id(ClientId::new(client_id))
|
||||||
|
.add_scope(Scope::new("profile".into()))
|
||||||
|
.add_scope(Scope::new("email".into()))
|
||||||
|
.with_client_secret(ClientSecret::new(client_secret))
|
||||||
|
.discover(IssuerUrl::new(issuer).expect("Invalid IssuerUrl"))
|
||||||
|
.await
|
||||||
|
.map_err(|e| mize_err!("oidc discover failed: {:?}", e))?
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let oidc_auth_service = ServiceBuilder::new()
|
||||||
|
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||||
|
dbg!(&e);
|
||||||
|
e.into_response()
|
||||||
|
}))
|
||||||
|
.layer(OidcAuthLayer::new(oidc_client));
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/foo", get(authenticated))
|
||||||
|
.route("/logout", get(logout))
|
||||||
|
.layer(oidc_login_service)
|
||||||
|
.route("/bar", get(maybe_authenticated))
|
||||||
|
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
|
||||||
|
.layer(oidc_auth_service)
|
||||||
|
.layer(session_layer)
|
||||||
|
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||||
|
.route("/", get(pages::main_page()))
|
||||||
|
.route("/mc", get(pages::mc_page()))
|
||||||
|
.route(
|
||||||
|
"/.well-known/matrix/client",
|
||||||
|
get(handle_matrix_client).layer(tower_http::cors::CorsLayer::permissive()),
|
||||||
|
)
|
||||||
|
.route("/.well-known/matrix/server", get(handle_matrix_server));
|
||||||
|
|
||||||
|
tracing::info!("ppc.server listening on http://{bind}");
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(&bind)
|
||||||
|
.await
|
||||||
|
.map_err(|e| mize_err!("bind {bind}: {e}"))?;
|
||||||
|
axum::serve(listener, app.into_make_service())
|
||||||
|
.await
|
||||||
|
.map_err(|e| mize_err!("axum serve: {e}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
|
||||||
|
format!("Hello {}", claims.subject().as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn maybe_authenticated(
|
||||||
|
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
if let Ok(claims) = claims {
|
||||||
|
format!(
|
||||||
|
"Hello {}! You are already logged in from another Handler.",
|
||||||
|
claims.subject().as_str()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"Hello anon!".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
|
||||||
|
logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix_client() -> Json<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"m.homeserver": {
|
||||||
|
"base_url": "https://matrix.ppc.social"
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix_server() -> Json<Value> {
|
||||||
|
Json(json!({
|
||||||
|
"m.server": "matrix.ppc.social:443"
|
||||||
|
}))
|
||||||
|
}
|
||||||
186
parts/ppc.server/src/pages.rs
Normal file
186
parts/ppc.server/src/pages.rs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
use axum::response::Html;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const SITE_CSS: &str = r#"
|
||||||
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
|
html { color-scheme: light dark; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 1.25rem 2.5rem;
|
||||||
|
max-width: 60ch;
|
||||||
|
margin-inline: auto;
|
||||||
|
font-family: Georgia, "Times New Roman", serif;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
background-color: #F7F8F4;
|
||||||
|
color: #20251C;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
h1, h2 {
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #20251C;
|
||||||
|
}
|
||||||
|
h1 { margin: 2.5rem 0 1.5rem; font-size: 1.75rem; }
|
||||||
|
h2 { margin: 2.5rem 0 1rem; font-size: 1.35rem; }
|
||||||
|
p { margin: 0.85rem 0; }
|
||||||
|
.tagline { color: #66705E; font-style: italic; }
|
||||||
|
.meta { color: #66705E; font-size: 0.95rem; }
|
||||||
|
a {
|
||||||
|
color: #376777;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
}
|
||||||
|
a:hover { color: #79500F; }
|
||||||
|
.accent { color: #79500F; }
|
||||||
|
.accent-bar {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 0.35rem;
|
||||||
|
background: #FEC251;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body { background-color: #171D18; color: #F2F4EA; }
|
||||||
|
h1, h2 { color: #F2F4EA; }
|
||||||
|
.tagline, .meta { color: #AAB5A4; }
|
||||||
|
a { color: #B9E4EB; }
|
||||||
|
a:hover { color: #D7E47D; }
|
||||||
|
.accent { color: #D7E47D; }
|
||||||
|
.accent-bar { background: #D7E47D; }
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const PLAUSIBLE_SRC: &str = "https://plausible.c2vi.dev/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js";
|
||||||
|
const PLAUSIBLE_BOOT: &str =
|
||||||
|
"window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }";
|
||||||
|
|
||||||
|
fn render_page(title: &str, body: Element) -> Html<String> {
|
||||||
|
let markup = dioxus::ssr::render_element(rsx! {
|
||||||
|
head {
|
||||||
|
meta { charset: "UTF-8" }
|
||||||
|
meta { name: "viewport", content: "width=device-width, initial-scale=1.0" }
|
||||||
|
title { "{title}" }
|
||||||
|
script {
|
||||||
|
defer: true,
|
||||||
|
"data-domain": "ppc.social",
|
||||||
|
src: PLAUSIBLE_SRC,
|
||||||
|
}
|
||||||
|
script { dangerous_inner_html: PLAUSIBLE_BOOT }
|
||||||
|
style { {SITE_CSS} }
|
||||||
|
}
|
||||||
|
body { {body} }
|
||||||
|
});
|
||||||
|
Html(format!("<!DOCTYPE html>\n<html lang=\"en\">\n{markup}\n</html>"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn MainPageBody() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div { class: "accent-bar" }
|
||||||
|
h1 { "People Corner" }
|
||||||
|
p { class: "tagline", "\"Cool People doing Cool things together\"" }
|
||||||
|
p {
|
||||||
|
"Read what this is all about: "
|
||||||
|
a { href: "https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC", "About" }
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
"There is a Discord Server: "
|
||||||
|
a { href: "https://discord.gg/cMKQNJMkRe", "https://discord.gg/cMKQNJMkRe" }
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
"There is a "
|
||||||
|
a { href: "https://ppc.social/mc", "Minecraft server" }
|
||||||
|
"."
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
"There is "
|
||||||
|
a { href: "https://wiki.ppc.social", "The PPC Wiki Projekt" }
|
||||||
|
"."
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
"There is an "
|
||||||
|
a { href: "https://www.instagram.com/ppc.social/", "Instagram" }
|
||||||
|
" and a "
|
||||||
|
a { href: "https://www.youtube.com/@ppc.social", "Youtube Channel" }
|
||||||
|
}
|
||||||
|
p { class: "meta", "and that's about all for now..." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn McPageBody() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div { class: "accent-bar" }
|
||||||
|
h1 { "Welcome to the PPC Minecraft Cluster" }
|
||||||
|
h2 { "Address" }
|
||||||
|
p { class: "accent", "first.ppc.social" }
|
||||||
|
p { class: "meta", "Java 1.21.11 and Bedrock (bedrock not working rn)" }
|
||||||
|
h2 { "Discord" }
|
||||||
|
p {
|
||||||
|
a {
|
||||||
|
href: "https://discord.gg/cMKQNJMkRe",
|
||||||
|
target: "_blank",
|
||||||
|
"https://discord.gg/cMKQNJMkRe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p { class: "meta", "come and say hi!" }
|
||||||
|
p { "See you ingame and invite all your friends to play as well!!" }
|
||||||
|
h2 { "SimpleVoicechat" }
|
||||||
|
p {
|
||||||
|
"This is "
|
||||||
|
span { class: "accent", "not required" }
|
||||||
|
", but makes playing soooo much more fun, so it's "
|
||||||
|
span { class: "accent", "highly encouraged" }
|
||||||
|
"."
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
"Instructions can be found here:"
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
a { href: "https://fabricmc.net/use/installer/", "Fabric" }
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
a {
|
||||||
|
href: "https://modrepo.de/minecraft/voicechat/wiki/installation#fabric",
|
||||||
|
"SimpleVoiceChat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_page() -> Html<String> {
|
||||||
|
render_page("People Corner", rsx! { MainPageBody {} })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mc_page() -> Html<String> {
|
||||||
|
render_page("PPC Minecraft Server", rsx! { McPageBody {} })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main_page_renders() {
|
||||||
|
let html = main_page().0;
|
||||||
|
assert!(html.contains("<!DOCTYPE html>"));
|
||||||
|
assert!(html.contains("People Corner"));
|
||||||
|
assert!(html.contains("Cool People doing Cool things together"));
|
||||||
|
assert!(html.contains("#F7F8F4"));
|
||||||
|
assert!(html.contains("#376777"));
|
||||||
|
assert!(html.contains("https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mc_page_renders() {
|
||||||
|
let html = mc_page().0;
|
||||||
|
assert!(html.contains("PPC Minecraft Cluster"));
|
||||||
|
assert!(html.contains("first.ppc.social"));
|
||||||
|
assert!(html.contains("SimpleVoicechat"));
|
||||||
|
assert!(html.contains("https://fabricmc.net/use/installer/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
19
parts/ppc/Cargo.toml
Normal file
19
parts/ppc/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "mize-part-ppc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mize_part_ppc"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { workspace = true }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
rustflags = ["-C", "prefer-dynamic"]
|
||||||
2
parts/ppc/part
Normal file
2
parts/ppc/part
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
id = "ppc"
|
||||||
|
gen = [ "spacetime" ]
|
||||||
7
parts/ppc/src/lib.rs
Normal file
7
parts/ppc/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use mize::prelude::*;
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn part_ppc() -> MizeResult<()> {
|
||||||
|
load_part_at("ppc.spacetime", "spacetime")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
19
parts/sci.inbox/Cargo.toml
Normal file
19
parts/sci.inbox/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "mize-part-sci-inbox"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mize_part_sci_inbox"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { workspace = true }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
rustflags = ["-C", "prefer-dynamic"]
|
||||||
6
parts/sci.inbox/src/lib.rs
Normal file
6
parts/sci.inbox/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use mize::prelude::*;
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn part_sci_inbox() -> MizeResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
19
parts/sci.timetrack/Cargo.toml
Normal file
19
parts/sci.timetrack/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "mize-part-sci-timetrack"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mize_part_sci_timetrack"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { workspace = true }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
rustflags = ["-C", "prefer-dynamic"]
|
||||||
73
parts/sci.timetrack/src/generated.rs
Normal file
73
parts/sci.timetrack/src/generated.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use mize::prelude::*;
|
||||||
|
|
||||||
|
pub struct CliPart {
|
||||||
|
id: MizeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MizePart for CliPart {
|
||||||
|
fn id(&self) -> MizeResult<MizeId> {
|
||||||
|
Ok(self.id.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliPart {
|
||||||
|
pub fn get(name: &str) -> MizeResult<CliPart> {
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
let part = mize.get_part(name)?;
|
||||||
|
Ok(CliPart { id: part.id()? })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_command(&self, name: &str) -> MizeResult<()> {
|
||||||
|
let mut args = ItemData::new();
|
||||||
|
args.set_path("0", name.into_item_data())?;
|
||||||
|
dyn_call(self.id.clone(), "set_command", args)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subcommand<T: Fn(ArgMatches) -> MizeResult<()> + Send + Sync + 'static>(
|
||||||
|
&self,
|
||||||
|
subcmd: Command,
|
||||||
|
action: T,
|
||||||
|
) -> MizeResult<()> {
|
||||||
|
let mut subcmd_arg = ItemData::new();
|
||||||
|
subcmd_arg.set_path("_native", subcmd.native_id)?;
|
||||||
|
let action_arg = ItemData::fun(Box::new(move |args: ItemData| {
|
||||||
|
let matches = args.clone().get_path("0")?.native_take_from::<ArgMatches>()?;
|
||||||
|
action(matches)?;
|
||||||
|
Ok(ItemData::new())
|
||||||
|
}))?;
|
||||||
|
let mut args = ItemData::new();
|
||||||
|
args.set_path("0", subcmd_arg)?;
|
||||||
|
args.set_path("1", action_arg)?;
|
||||||
|
dyn_call(self.id.clone(), "subcommand", args)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Command {
|
||||||
|
native_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArgMatches {
|
||||||
|
native_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromItemData for ArgMatches {
|
||||||
|
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
|
||||||
|
Ok(ArgMatches {
|
||||||
|
native_id: data.get_path("_native")?.value_u32()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn new(name: &str) -> MizeResult<Command> {
|
||||||
|
let mut args = ItemData::new();
|
||||||
|
args.set_path("0", name.into_item_data())?;
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
let id = mize.id_new_dots("cli.Command")?;
|
||||||
|
let data = dyn_call(id, "new", args)?;
|
||||||
|
let native_id: u32 = data.get_path("_native")?.native_take()?;
|
||||||
|
Ok(Command { native_id })
|
||||||
|
}
|
||||||
|
}
|
||||||
65
parts/sci.timetrack/src/lib.rs
Normal file
65
parts/sci.timetrack/src/lib.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
mod generated;
|
||||||
|
|
||||||
|
use generated::{ArgMatches, CliPart, Command};
|
||||||
|
use mize::prelude::*;
|
||||||
|
use mize::proto::MizeMessage;
|
||||||
|
|
||||||
|
fn ensure_default(rel: &str, default: ItemData) -> MizeResult<()> {
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
let cur = mize
|
||||||
|
.cur_eval_part()?
|
||||||
|
.ok_or_else(|| mize_err!("no cur_eval_part"))?;
|
||||||
|
let full = format!("{}.{}", cur.dot(), rel);
|
||||||
|
if mize.get_config(&full).is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let segs: Vec<&str> = rel.split('.').filter(|s| !s.is_empty()).collect();
|
||||||
|
let last = *segs.last().ok_or_else(|| mize_err!("empty opt path"))?;
|
||||||
|
let mut parent = cur.clone();
|
||||||
|
for seg in &segs[..segs.len() - 1] {
|
||||||
|
parent = parent.join(seg)?;
|
||||||
|
}
|
||||||
|
let original = Some(cur);
|
||||||
|
mize.set_cur_eval_part(Some(parent))?;
|
||||||
|
opt(last).default_val(default);
|
||||||
|
mize.set_cur_eval_part(original)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spacetime_unwired(_: ArgMatches) -> MizeResult<()> {
|
||||||
|
Err(mize_err!("spacetime SDK not wired yet"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn part_sci_timetrack() -> MizeResult<()> {
|
||||||
|
ensure_default("daily_notes_path", "".into_item_data())?;
|
||||||
|
ensure_default("profile", "c2vi".into_item_data())?;
|
||||||
|
|
||||||
|
let cli = CliPart::get("cli")?;
|
||||||
|
cli.set_command("sci")?;
|
||||||
|
|
||||||
|
for (name, alias) in [
|
||||||
|
("editActionList", "aal"),
|
||||||
|
("nextAction", "ne"),
|
||||||
|
("printCurrentAction", "cu"),
|
||||||
|
("listActions", "la"),
|
||||||
|
("listActivities", "lac"),
|
||||||
|
("editActivities", "eac"),
|
||||||
|
("timetrackExport", "te"),
|
||||||
|
] {
|
||||||
|
cli.subcommand(Command::new(name)?, spacetime_unwired)?;
|
||||||
|
cli.subcommand(Command::new(alias)?, spacetime_unwired)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
mize.add_listener(
|
||||||
|
"timetrack.sub_current",
|
||||||
|
Box::new(|_msg: MizeMessage, _mize: &mut Mize| {
|
||||||
|
Err(mize_err!(
|
||||||
|
"spacetime SDK not wired yet (timetrack.sub_current)"
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
20
parts/sci/Cargo.toml
Normal file
20
parts/sci/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "mize-part-sci"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mize_part_sci"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { workspace = true }
|
||||||
|
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
rustflags = ["-C", "prefer-dynamic"]
|
||||||
2
parts/sci/part
Normal file
2
parts/sci/part
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
id = "sci"
|
||||||
|
gen = [ "cli", "sci", "spacetime" ]
|
||||||
1119
parts/sci/spacetime/Cargo.lock
generated
Normal file
1119
parts/sci/spacetime/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
parts/sci/spacetime/Cargo.toml
Normal file
17
parts/sci/spacetime/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "mize-spacetime"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# Standalone SpacetimeDB module crate (not a mize workspace member / not a dep of sci).
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
spacetimedb = { version = "=2.6.0", features = ["unstable"] }
|
||||||
|
log = "0.4"
|
||||||
|
chrono = { version = "0.4.45", features = [], default-features = false }
|
||||||
|
chrono-tz = { version = "0.10.4", default-features = false }
|
||||||
|
url = "2.5.8"
|
||||||
394
parts/sci/spacetime/src/lib.rs
Normal file
394
parts/sci/spacetime/src/lib.rs
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use chrono::{DateTime, TimeZone};
|
||||||
|
use chrono_tz::Europe;
|
||||||
|
use log::*;
|
||||||
|
use spacetimedb::SpacetimeType;
|
||||||
|
use spacetimedb::sys::raw::index_id_from_name;
|
||||||
|
use spacetimedb::{Identity, ReducerContext, ProcedureContext, Table, Timestamp, reducer, table, procedure};
|
||||||
|
use spacetimedb::http::*;
|
||||||
|
|
||||||
|
#[table(accessor = profiles, public)]
|
||||||
|
pub struct Profile {
|
||||||
|
#[primary_key]
|
||||||
|
identity: Identity,
|
||||||
|
#[unique]
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[table(accessor = task_part, public)]
|
||||||
|
pub struct TaskPart {
|
||||||
|
#[primary_key]
|
||||||
|
identity: Identity,
|
||||||
|
start_of_current: Timestamp,
|
||||||
|
current: Action,
|
||||||
|
upcoming: Vec<Action>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[table(accessor = activities, public)]
|
||||||
|
pub struct Activity {
|
||||||
|
#[primary_key]
|
||||||
|
#[auto_inc]
|
||||||
|
id: u64,
|
||||||
|
name: String,
|
||||||
|
task: Option<u64>,
|
||||||
|
parents_ids: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[table(accessor = tasks, public)]
|
||||||
|
pub struct Task {
|
||||||
|
#[primary_key]
|
||||||
|
#[auto_inc]
|
||||||
|
id: u64,
|
||||||
|
done: bool,
|
||||||
|
name: String,
|
||||||
|
parent: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[table(accessor = thoughts, public)]
|
||||||
|
pub struct Thought {
|
||||||
|
#[primary_key]
|
||||||
|
#[auto_inc]
|
||||||
|
id: u64,
|
||||||
|
text: String,
|
||||||
|
user: Identity,
|
||||||
|
#[index(btree)]
|
||||||
|
time: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[table(accessor = action)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Action {
|
||||||
|
name: String,
|
||||||
|
activity: Option<u64>,
|
||||||
|
task: Option<u64>,
|
||||||
|
start: Option<Timestamp>,
|
||||||
|
end: Option<Timestamp>,
|
||||||
|
duration: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action {
|
||||||
|
pub fn name_only(name: String) -> Self {
|
||||||
|
Action {
|
||||||
|
name,
|
||||||
|
activity: None,
|
||||||
|
task: None,
|
||||||
|
start: None,
|
||||||
|
end: None,
|
||||||
|
duration: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
pub fn add_thought(ctx: &ReducerContext, thought: String) -> Result<(), String> {
|
||||||
|
ctx.db.thoughts().try_insert(Thought {
|
||||||
|
id: 0,
|
||||||
|
text: thought,
|
||||||
|
user: ctx.sender(),
|
||||||
|
time: ctx.timestamp,
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[table(accessor = log_events, public, index(accessor = log_events_index, btree(columns = [user, end])))]
|
||||||
|
pub struct LogEvent {
|
||||||
|
#[primary_key]
|
||||||
|
#[auto_inc]
|
||||||
|
id: u64,
|
||||||
|
start: Timestamp,
|
||||||
|
end: Timestamp,
|
||||||
|
action: Action,
|
||||||
|
user: Identity,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer(init)]
|
||||||
|
pub fn init(_ctx: &ReducerContext) {
|
||||||
|
// Called when the module is initially published
|
||||||
|
info!("hiiiiiii from ppc module");
|
||||||
|
}
|
||||||
|
|
||||||
|
// the one from ppc, hardcoded rn, as we for now only support config at run time.
|
||||||
|
// this needs to be loaded at build time. (this code runs as a wasm module on the spacetimedb server)
|
||||||
|
const PPC_AUTH_CLIENT_ID: &str = "363331597203734531";
|
||||||
|
|
||||||
|
#[reducer(client_connected)]
|
||||||
|
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
|
||||||
|
check_auth(ctx)?;
|
||||||
|
info!("hooooo");
|
||||||
|
|
||||||
|
// add user
|
||||||
|
let _ = ctx.db.profiles().try_insert(Profile {
|
||||||
|
identity: ctx.sender(),
|
||||||
|
username: ctx.sender().to_string(),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_auth(ctx: &ReducerContext) -> Result<(), String> {
|
||||||
|
let jwt = ctx
|
||||||
|
.sender_auth()
|
||||||
|
.jwt()
|
||||||
|
.ok_or("Authentication required".to_string())?;
|
||||||
|
if jwt.issuer() != "https://auth.ppc.social" {
|
||||||
|
return Err("Invalid issuer".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !jwt.audience().iter().any(|a| a == PPC_AUTH_CLIENT_ID) {
|
||||||
|
return Err("Invalid audience".to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer(client_disconnected)]
|
||||||
|
pub fn identity_disconnected(_ctx: &ReducerContext) {
|
||||||
|
// Called everytime a client disconnects
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
pub fn add_activity(ctx: &ReducerContext, name: String) {
|
||||||
|
ctx.db.activities().insert(Activity {
|
||||||
|
name,
|
||||||
|
task: None,
|
||||||
|
id: 0, // auto-increments
|
||||||
|
parents_ids: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
pub fn set_activity_name(ctx: &ReducerContext, id: u64, name: String) {
|
||||||
|
if let Some(old_ac) = ctx.db.activities().id().find(id) {
|
||||||
|
ctx.db.activities().id().update(Activity { name, ..old_ac });
|
||||||
|
} else {
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
pub fn update_upcoming(ctx: &ReducerContext, upcoming: Vec<Action>) {
|
||||||
|
if ctx.db.task_part().identity().find(ctx.sender()).is_none() {
|
||||||
|
ctx.db.task_part().insert(TaskPart {
|
||||||
|
upcoming,
|
||||||
|
identity: ctx.sender(),
|
||||||
|
current: Action {
|
||||||
|
name: "filler".to_string(),
|
||||||
|
activity: None,
|
||||||
|
task: None,
|
||||||
|
start: None,
|
||||||
|
end: None,
|
||||||
|
duration: None,
|
||||||
|
},
|
||||||
|
start_of_current: ctx.timestamp,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ctx.db.task_part().identity().update(TaskPart {
|
||||||
|
upcoming,
|
||||||
|
..ctx.db.task_part().identity().find(&ctx.sender()).unwrap()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
pub fn next_action(ctx: &ReducerContext, break_flag: bool, action: Option<Action>) -> Result<(), String> {
|
||||||
|
if let Some(mut task_part) = ctx.db.task_part().identity().find(&ctx.sender()) {
|
||||||
|
if task_part.upcoming.is_empty() {
|
||||||
|
return Err("No upcoming actions".to_string());
|
||||||
|
}
|
||||||
|
let new_action = match action {
|
||||||
|
Some(action) => action,
|
||||||
|
// when we do "ppc nextAction -b" ... we want to do a generic break "b" action.
|
||||||
|
None => if break_flag { Action::name_only("idk".to_string()) } else { task_part.upcoming.remove(0) },
|
||||||
|
};
|
||||||
|
// when break flag, push current_action again to the front of upcoming
|
||||||
|
if break_flag {
|
||||||
|
// insert generic "b" action when no one is specified
|
||||||
|
task_part.upcoming.insert(0, task_part.current.clone());
|
||||||
|
}
|
||||||
|
let start_of_old_action = task_part.start_of_current;
|
||||||
|
ctx.db.task_part().identity().update(TaskPart {
|
||||||
|
upcoming: task_part.upcoming,
|
||||||
|
identity: task_part.identity,
|
||||||
|
current: new_action.clone(),
|
||||||
|
start_of_current: ctx.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
// create log event
|
||||||
|
ctx.db.log_events().insert(LogEvent {
|
||||||
|
start: start_of_old_action,
|
||||||
|
end: ctx.timestamp,
|
||||||
|
action: task_part.current,
|
||||||
|
id: 0,
|
||||||
|
user: ctx.sender(),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Task part data not found for this user".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
pub fn update_activity(ctx: &ReducerContext, activity: Activity) {
|
||||||
|
if activity.id == 0 {
|
||||||
|
ctx.db.activities().insert(activity);
|
||||||
|
} else {
|
||||||
|
ctx.db.activities().id().update(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
pub fn remove_activity(ctx: &ReducerContext, activity: Activity) {
|
||||||
|
ctx.db.activities().delete(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////// daily metric ///////////////////
|
||||||
|
|
||||||
|
#[table(accessor = daily_task, public)]
|
||||||
|
pub struct DailyTask {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
owner: Identity,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[table(accessor = daily_task_log, public)]
|
||||||
|
pub struct DailyTaskLog {
|
||||||
|
daily_task_id: i64,
|
||||||
|
done: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[procedure]
|
||||||
|
fn first_timetrack_date(ctx: &mut ProcedureContext, username: String) -> Result<String, String> {
|
||||||
|
let timestamp = ctx.try_with_tx(|tx| {
|
||||||
|
let profile = tx.db.profiles().username().find(username.clone()).ok_or("No such Profile".to_string())?;
|
||||||
|
let event = tx.db.log_events()
|
||||||
|
.log_events_index()
|
||||||
|
.filter(&profile.identity)
|
||||||
|
.nth(0)
|
||||||
|
.ok_or("Profile has no row in log_events")?;
|
||||||
|
let time = event.start
|
||||||
|
.to_chrono_date_time().map_err(|e| format!("chrono error: {e}"))?
|
||||||
|
.with_timezone(&chrono_tz::Europe::Vienna);
|
||||||
|
Ok::<String, String>(time.format("%Y-%m-%d").to_string())
|
||||||
|
})?;
|
||||||
|
Ok(timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[procedure]
|
||||||
|
fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String) -> Result<String, String> {
|
||||||
|
// CEST only rn
|
||||||
|
let start = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(5, 0, 0);
|
||||||
|
let start_local = chrono_tz::Europe::Vienna.from_local_datetime(&start).unwrap();
|
||||||
|
let start_utc = start_local.with_timezone(&chrono::Utc);
|
||||||
|
let start_timestamp = Timestamp::from_micros_since_unix_epoch(start_utc.timestamp_micros());
|
||||||
|
|
||||||
|
let end = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(4, 0, 0) + Duration::from_hours(24);
|
||||||
|
let end_local = chrono_tz::Europe::Vienna.from_local_datetime(&end).unwrap();
|
||||||
|
let end_utc = end_local.with_timezone(&chrono::Utc);
|
||||||
|
let end_timestamp = Timestamp::from_micros_since_unix_epoch(end_utc.timestamp_micros());
|
||||||
|
|
||||||
|
info!("start_of_day: {}", start_timestamp);
|
||||||
|
info!("end_of_day: {}", end_timestamp);
|
||||||
|
|
||||||
|
let mut export = String::new();
|
||||||
|
//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))?;
|
||||||
|
|
||||||
|
let events = ctx.with_tx(|tx|
|
||||||
|
tx.db.log_events()
|
||||||
|
.log_events_index()
|
||||||
|
.filter((profile.identity, start_timestamp..end_timestamp))
|
||||||
|
);
|
||||||
|
for event in events {
|
||||||
|
let start = event.start
|
||||||
|
.to_chrono_date_time()
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
.with_timezone(&Europe::Vienna)
|
||||||
|
.format("%H:%M");
|
||||||
|
let end = event.end
|
||||||
|
.to_chrono_date_time()
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
.with_timezone(&Europe::Vienna)
|
||||||
|
.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| {
|
||||||
|
tx.db.thoughts().time().filter(start_timestamp..end_timestamp)
|
||||||
|
});
|
||||||
|
for thought in thoughts {
|
||||||
|
let line = format!("- {}\n\n", thought.text);
|
||||||
|
export.push_str(line.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(export)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[procedure]
|
||||||
|
pub fn migrate(ctx: &mut ProcedureContext) -> String {
|
||||||
|
info!("migrate procedure called");
|
||||||
|
//let msg = "Nothing to migrate".to_string();
|
||||||
|
|
||||||
|
let msg: Result<String, String> = ctx.try_with_tx(|tx| {
|
||||||
|
let mut out = String::new();
|
||||||
|
let mut count = 0;
|
||||||
|
//for event in tx.db.log_events_tmp().iter() {
|
||||||
|
//count += 1;
|
||||||
|
//}
|
||||||
|
out.push_str(format!("Migrated {} rows................", count).as_str());
|
||||||
|
Ok(out)
|
||||||
|
});
|
||||||
|
|
||||||
|
return match msg {
|
||||||
|
Ok(msg) => msg,
|
||||||
|
Err(err) => err,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[procedure]
|
||||||
|
pub fn test(ctx: &mut ProcedureContext) -> String {
|
||||||
|
let mut s = String::from("hoooooooooooooooooooooo\n");
|
||||||
|
let a: String = ctx.with_tx(|_tx| {
|
||||||
|
let s = String::new();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
s.push_str(&format!("{}\n\n", a));
|
||||||
|
s.push_str("\n");
|
||||||
|
s.push_str(&format!("sender: {}", ctx.sender()));
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
#[handler]
|
||||||
|
pub fn test_handler(ctx: &mut HandlerContext, _req: Request<Body>) -> Response<Body> {
|
||||||
|
let res = ctx.try_with_tx(|tx| {
|
||||||
|
let mut s = String::new();
|
||||||
|
for a in tx.db.profiles().iter() {
|
||||||
|
s.push_str(&format!("{} ... {}\n", a.identity, a.username));
|
||||||
|
}
|
||||||
|
s.push_str(&format!("sender: {}", tx.sender()));
|
||||||
|
return Ok(s);
|
||||||
|
});
|
||||||
|
return Response::new(Body::from_bytes(match res {
|
||||||
|
Ok(body) => body,
|
||||||
|
Err(e) => e,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[spacetimedb::http::router]
|
||||||
|
fn router() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.get("/test", test_handler)
|
||||||
|
}
|
||||||
73
parts/sci/src/generated.rs
Normal file
73
parts/sci/src/generated.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use mize::prelude::*;
|
||||||
|
|
||||||
|
pub struct CliPart {
|
||||||
|
id: MizeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MizePart for CliPart {
|
||||||
|
fn id(&self) -> MizeResult<MizeId> {
|
||||||
|
Ok(self.id.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliPart {
|
||||||
|
pub fn get(name: &str) -> MizeResult<CliPart> {
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
let part = mize.get_part(name)?;
|
||||||
|
Ok(CliPart { id: part.id()? })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_command(&self, name: &str) -> MizeResult<()> {
|
||||||
|
let mut args = ItemData::new();
|
||||||
|
args.set_path("0", name.into_item_data())?;
|
||||||
|
dyn_call(self.id.clone(), "set_command", args)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subcommand<T: Fn(ArgMatches) -> MizeResult<()> + Send + Sync + 'static>(
|
||||||
|
&self,
|
||||||
|
subcmd: Command,
|
||||||
|
action: T,
|
||||||
|
) -> MizeResult<()> {
|
||||||
|
let mut subcmd_arg = ItemData::new();
|
||||||
|
subcmd_arg.set_path("_native", subcmd.native_id)?;
|
||||||
|
let action_arg = ItemData::fun(Box::new(move |args: ItemData| {
|
||||||
|
let matches = args.clone().get_path("0")?.native_take_from::<ArgMatches>()?;
|
||||||
|
action(matches)?;
|
||||||
|
Ok(ItemData::new())
|
||||||
|
}))?;
|
||||||
|
let mut args = ItemData::new();
|
||||||
|
args.set_path("0", subcmd_arg)?;
|
||||||
|
args.set_path("1", action_arg)?;
|
||||||
|
dyn_call(self.id.clone(), "subcommand", args)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Command {
|
||||||
|
native_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArgMatches {
|
||||||
|
native_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromItemData for ArgMatches {
|
||||||
|
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
|
||||||
|
Ok(ArgMatches {
|
||||||
|
native_id: data.get_path("_native")?.value_u32()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn new(name: &str) -> MizeResult<Command> {
|
||||||
|
let mut args = ItemData::new();
|
||||||
|
args.set_path("0", name.into_item_data())?;
|
||||||
|
let mut mize = get_global_mize()?;
|
||||||
|
let id = mize.id_new_dots("cli.Command")?;
|
||||||
|
let data = dyn_call(id, "new", args)?;
|
||||||
|
let native_id: u32 = data.get_path("_native")?.native_take()?;
|
||||||
|
Ok(Command { native_id })
|
||||||
|
}
|
||||||
|
}
|
||||||
37
parts/sci/src/lib.rs
Normal file
37
parts/sci/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
mod generated;
|
||||||
|
|
||||||
|
use generated::{ArgMatches, CliPart, Command};
|
||||||
|
use mize::prelude::*;
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn part_sci() -> MizeResult<()> {
|
||||||
|
load_part_at("priv.spacetime", "spacetime")?;
|
||||||
|
|
||||||
|
let cli = CliPart::get("cli")?;
|
||||||
|
cli.set_command("sci")?;
|
||||||
|
|
||||||
|
cli.subcommand(Command::new("inputThought")?, input_thought)?;
|
||||||
|
cli.subcommand(Command::new("ti")?, input_thought)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_thought(_matches: ArgMatches) -> MizeResult<()> {
|
||||||
|
let tmp_file = std::env::temp_dir().join(format!(
|
||||||
|
"sc2-input-thought-{}.txt",
|
||||||
|
chrono::Utc::now().timestamp()
|
||||||
|
));
|
||||||
|
let status = std::process::Command::new("nvim")
|
||||||
|
.arg(&tmp_file)
|
||||||
|
.status()
|
||||||
|
.map_err(|e| mize_err!("failed to spawn nvim: {}", e))?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(mize_err!("nvim exited with {}", status));
|
||||||
|
}
|
||||||
|
let _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);
|
||||||
|
|
||||||
|
// spacetime SDK not wired yet — reducer add_thought comes later
|
||||||
|
Err(mize_err!("spacetime SDK not wired yet (add_thought)"))
|
||||||
|
}
|
||||||
19
parts/spacetime/Cargo.toml
Normal file
19
parts/spacetime/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "mize-part-spacetime"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mize_part_spacetime"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mize = { workspace = true }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
rustflags = ["-C", "prefer-dynamic"]
|
||||||
9
parts/spacetime/src/lib.rs
Normal file
9
parts/spacetime/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use mize::prelude::*;
|
||||||
|
|
||||||
|
/// Stub SpacetimeDB client part.
|
||||||
|
/// SDK wiring (connect with auth.client.get_id_token) comes later; dependents may fail until then.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn part_spacetime() -> MizeResult<()> {
|
||||||
|
opt("url");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user