Compare commits
31 Commits
562b1abe9c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0abf4108e8 | ||
|
|
9707286375 | ||
|
|
6eedb1826a | ||
|
|
66b8713e3a | ||
|
|
37cb94d1d4 | ||
|
|
9141fc5441 | ||
|
|
2b2d40148b | ||
|
|
9078b108a6 | ||
|
|
6bd98e7c80 | ||
|
|
9e5ceaf0f9 | ||
|
|
48f7ee2e35 | ||
|
|
387c31259d | ||
|
|
7f86f3dc5e | ||
|
|
679f5e51e5 | ||
|
|
105eca862d | ||
|
|
0b316ad54f | ||
|
|
a7db7da9d2 | ||
|
|
759e6cad66 | ||
|
|
c19de1ed84 | ||
|
|
e96e93a5a0 | ||
|
|
d559159397 | ||
|
|
05d410949c | ||
|
|
b2ed9efff2 | ||
|
|
c57849f748 | ||
|
|
19573502d2 | ||
|
|
d1ddb405c8 | ||
|
|
2907d7e236 | ||
|
|
55c85ad716 | ||
|
|
193c3ce3e0 | ||
|
|
5599a48c27 | ||
|
|
c9ea571744 |
72
AGENTS.md
72
AGENTS.md
@@ -1,62 +1,24 @@
|
||||
# Mize
|
||||
|
||||
Mize is a strongly typed "filesystem" for the age of connectivity, elevating the Unix file philosophy into modern distributed computing.
|
||||
Mize is many things.
|
||||
- a "strongly typed filesystem for the age of everyone having many devices"
|
||||
- a stdlib replacement
|
||||
- a framework to make malleable/extensible/configurable/debugable software fast
|
||||
- a OS userspace (mainly linux but later also any other kernels)
|
||||
- a library for COP = Configuration Oriented Programming
|
||||
- a thing providing one single API through which any data can be interacted with
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
packages/
|
||||
├── mize/ # Core Mize framework (Rust)
|
||||
├── marts/ # Mize parts collection (Rust/TypeScript)
|
||||
├── mme/ # Mize Module Environment (Rust)
|
||||
├── ppc/ # Platform-specific components
|
||||
├── vic/ # Victor CLI tool
|
||||
└── ac_mize_macros/# Mize macros
|
||||
core/ # Core Mize framework (Rust)
|
||||
parts/ # Mize parts (cdylibs / crates)
|
||||
macros/ # Proc macros
|
||||
docs/ # documentation
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
To understand the main components of the architecture you canr efer to `architecture.md`.
|
||||
|
||||
## Require clarification and plan approval before making code changes
|
||||
|
||||
Before making any code changes other than the changelog, 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
|
||||
|
||||
### 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
|
||||
|
||||
### 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].
|
||||
|
||||
6058
Cargo.lock
generated
6058
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
23
Cargo.toml
@@ -1,9 +1,22 @@
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"packages/ac_mize_macros",
|
||||
"packages/marts",
|
||||
"packages/mize",
|
||||
"packages/ppc",
|
||||
"packages/ppc/spacetimedb",
|
||||
"macros",
|
||||
"apps/os",
|
||||
"apps/obsidian-plugin",
|
||||
"parts/*",
|
||||
"parts/mize.ui/dioxus-components",
|
||||
]
|
||||
exclude = [
|
||||
"parts/mos",
|
||||
# "parts/mme",
|
||||
]
|
||||
default-members = [
|
||||
"apps/obsidian-plugin",
|
||||
"apps/os",
|
||||
"parts/*",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
mize = { path = "core" }
|
||||
|
||||
47
README.md
47
README.md
@@ -1,25 +1,32 @@
|
||||
# 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?
|
||||
- What annoys me about software these days is, that things are done in so many different ways.
|
||||
- e.g: Different cloud applications for syncing data (Google Cloud, OneDrive, Dropbox)
|
||||
- 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)
|
||||
## Mize is many things
|
||||
- a "strongly typed filesystem for the age of everyone having many devices"
|
||||
- "eelevate the UNIX philosophy into the age of having multiple connected devices"
|
||||
- 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.
|
||||
- (just as simple bytestreams/files, organized by a folder-structure)
|
||||
- and NO program stored and synced any data around in unpredictable ways.
|
||||
|
||||
- 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).
|
||||
|
||||
- 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._
|
||||
## things
|
||||
- [MiZe Parts](https://wiki.ppc.social/u/c2vi/MiZe-Part)
|
||||
- [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)
|
||||
- [mize lang](https://wiki.ppc.social/u/c2vi/mize-lang)
|
||||
- [Mize Software Factory](https://wiki.ppc.social/Mize-Software-Factory)
|
||||
|
||||
- [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
|
||||
```
|
||||
7
apps/obsidian-plugin/Cargo.toml
Normal file
7
apps/obsidian-plugin/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "mize-obsidian-plugin"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
mize = { workspace = true }
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@ppc/obsidian-plugin",
|
||||
"tasks": {
|
||||
"build_rust": "wasm-pack -vvvvv build -d platform/obsidian/rust_dist --target web --no-default-features --features target-obsidian",
|
||||
"build_rust": "CARGO_TERM_COLOR=always wasm-pack -vvvvv build -d ../apps/obsidian-plugin/rust_dist --target web --no-opt ../../marts --no-default-features --features target-obsidian",
|
||||
"build": "deno run -A build_rust && mkdir -p dist && touch dist/.hotreload && cp manifest.json dist && deno -A esbuild.config.mjs production",
|
||||
"dev": ""
|
||||
},
|
||||
@@ -1,36 +1,45 @@
|
||||
{
|
||||
"version": "5",
|
||||
"specifiers": {
|
||||
"jsr:@deno/esbuild-plugin@^1.2.1": "1.2.1",
|
||||
"jsr:@deno/loader@~0.3.10": "0.3.12",
|
||||
"jsr:@std/internal@^1.0.12": "1.0.12",
|
||||
"jsr:@std/path@^1.1.1": "1.1.4",
|
||||
"npm:@flowershow/remark-wiki-link@^3.4.0": "3.4.0",
|
||||
"npm:@instantdb/admin@~0.21.3": "0.21.30",
|
||||
"npm:@instantdb/react@~0.20.28": "0.20.28_react@19.1.0",
|
||||
"npm:@react-oauth/google@~0.12.2": "0.12.2_react@19.1.0_react-dom@19.1.0__react@19.1.0",
|
||||
"npm:@tailwindcss/postcss@4": "4.2.0",
|
||||
"npm:@types/node@20": "20.19.33",
|
||||
"npm:@types/react-dom@19": "19.2.3_@types+react@19.2.14",
|
||||
"npm:@types/react@19": "19.2.14",
|
||||
"npm:@wasm-tool/rollup-plugin-rust@^3.1.4": "3.1.4_binaryen@125.0.0",
|
||||
"npm:binaryen@125": "125.0.0",
|
||||
"npm:builtin-modules@5": "5.0.0",
|
||||
"npm:esbuild@~0.27.3": "0.27.3",
|
||||
"npm:mdast@3": "3.0.0",
|
||||
"npm:obsidian@^1.12.2": "1.12.2_@codemirror+state@6.5.0_@codemirror+view@6.38.6",
|
||||
"npm:react-dom@19.1.0": "19.1.0_react@19.1.0",
|
||||
"npm:react@19.1.0": "19.1.0",
|
||||
"npm:remark-frontmatter@5": "5.0.0",
|
||||
"npm:remark-gfm@^4.0.1": "4.0.1",
|
||||
"npm:remark-parse@11": "11.0.0",
|
||||
"npm:remark-stringify@11": "11.0.0",
|
||||
"npm:tailwindcss@4": "4.2.0",
|
||||
"npm:tslib@^2.6.2": "2.8.1",
|
||||
"npm:typescript@^5.9.3": "5.9.3",
|
||||
"npm:unified@^11.0.5": "11.0.5",
|
||||
"npm:unist-util-visit@^5.1.0": "5.1.0"
|
||||
},
|
||||
"npm": {
|
||||
"@alloc/quick-lru@5.2.0": {
|
||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="
|
||||
"jsr": {
|
||||
"@deno/esbuild-plugin@1.2.1": {
|
||||
"integrity": "df629467913adc1f960149fdfa3a3430ba8c20381c310fba096db244e6c3c9f6",
|
||||
"dependencies": [
|
||||
"jsr:@deno/loader",
|
||||
"jsr:@std/path"
|
||||
]
|
||||
},
|
||||
"@deno/loader@0.3.12": {
|
||||
"integrity": "52d3b3be0a32192efe07b0a4f1b3047077d2f2bba0f693e32f47421507f9fdb6"
|
||||
},
|
||||
"@std/internal@1.0.12": {
|
||||
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
|
||||
},
|
||||
"@std/path@1.1.4": {
|
||||
"integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal"
|
||||
]
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@codemirror/state@6.5.0": {
|
||||
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
|
||||
"dependencies": [
|
||||
@@ -185,196 +194,9 @@
|
||||
"micromark-util-symbol"
|
||||
]
|
||||
},
|
||||
"@iarna/toml@2.2.5": {
|
||||
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
|
||||
},
|
||||
"@instantdb/admin@0.21.30": {
|
||||
"integrity": "sha512-IR8Wi+wJkGVqClJUnEFkUXYScyhmUV1qPO4s1+eHfFptkquHHtGSz1bUffGbGZJCj84lJVpjcvS1MCPgvd2PBg==",
|
||||
"dependencies": [
|
||||
"@instantdb/core@0.21.30",
|
||||
"@instantdb/version@0.21.30",
|
||||
"eventsource"
|
||||
]
|
||||
},
|
||||
"@instantdb/core@0.20.28": {
|
||||
"integrity": "sha512-0ePAgbOZedSLZ4LbPZL6afcFnhL5mWIoWXgOqzdmp6QUrYgtQjWrJCcFlA7DWmrkidZYLU2WVpQbhMbgzKqrOg==",
|
||||
"dependencies": [
|
||||
"@instantdb/version@0.20.28",
|
||||
"mutative",
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"@instantdb/core@0.21.30": {
|
||||
"integrity": "sha512-PB/QFtrOYRmjKDYzo7oOIuJalLJVY/4mLljcYFkrbShMKz1oukKQ8sy+0/9pVcUETMyePgJUmuXcnrHwO7xjgQ==",
|
||||
"dependencies": [
|
||||
"@instantdb/version@0.21.30",
|
||||
"mutative",
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"@instantdb/react@0.20.28_react@19.1.0": {
|
||||
"integrity": "sha512-wPig+jUeUqSCYGdCLIGVky8bL0h3VnjYYD2+5SZtF/KlgFGQZ+u8LCdvk+8QJL/kE0xqDbogr7D+b7Vn73uO/A==",
|
||||
"dependencies": [
|
||||
"@instantdb/core@0.20.28",
|
||||
"@instantdb/version@0.20.28",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"@instantdb/version@0.20.28": {
|
||||
"integrity": "sha512-lwsF66TpVwfBOSQ+bpuS2K1NO7jwlnJS5Uh4Xhyylwu4ieOEOOZa2vMnVORsidZCq2vwwKEiLvFIoH8lVkrj0g=="
|
||||
},
|
||||
"@instantdb/version@0.21.30": {
|
||||
"integrity": "sha512-yhrxnZo3c2/EK3dnDEcX4XYHfXjlyj3QPPvylrBY2qxodKzcEh9BnJufiQiP+2ePUD+kET+2FQlztxQ4E9BAbg=="
|
||||
},
|
||||
"@isaacs/fs-minipass@4.0.1": {
|
||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
||||
"dependencies": [
|
||||
"minipass"
|
||||
]
|
||||
},
|
||||
"@jridgewell/gen-mapping@0.3.13": {
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dependencies": [
|
||||
"@jridgewell/sourcemap-codec",
|
||||
"@jridgewell/trace-mapping"
|
||||
]
|
||||
},
|
||||
"@jridgewell/remapping@2.3.5": {
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dependencies": [
|
||||
"@jridgewell/gen-mapping",
|
||||
"@jridgewell/trace-mapping"
|
||||
]
|
||||
},
|
||||
"@jridgewell/resolve-uri@3.1.2": {
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
|
||||
},
|
||||
"@jridgewell/sourcemap-codec@1.5.5": {
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
|
||||
},
|
||||
"@jridgewell/trace-mapping@0.3.31": {
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dependencies": [
|
||||
"@jridgewell/resolve-uri",
|
||||
"@jridgewell/sourcemap-codec"
|
||||
]
|
||||
},
|
||||
"@marijn/find-cluster-break@1.0.2": {
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||
},
|
||||
"@react-oauth/google@0.12.2_react@19.1.0_react-dom@19.1.0__react@19.1.0": {
|
||||
"integrity": "sha512-d1GVm2uD4E44EJft2RbKtp8Z1fp/gK8Lb6KHgs3pHlM0PxCXGLaq8LLYQYENnN4xPWO1gkL4apBtlPKzpLvZwg==",
|
||||
"dependencies": [
|
||||
"react",
|
||||
"react-dom"
|
||||
]
|
||||
},
|
||||
"@rollup/pluginutils@5.3.0": {
|
||||
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||
"dependencies": [
|
||||
"@types/estree",
|
||||
"estree-walker",
|
||||
"picomatch"
|
||||
]
|
||||
},
|
||||
"@tailwindcss/node@4.2.0": {
|
||||
"integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==",
|
||||
"dependencies": [
|
||||
"@jridgewell/remapping",
|
||||
"enhanced-resolve",
|
||||
"jiti",
|
||||
"lightningcss",
|
||||
"magic-string",
|
||||
"source-map-js",
|
||||
"tailwindcss"
|
||||
]
|
||||
},
|
||||
"@tailwindcss/oxide-android-arm64@4.2.0": {
|
||||
"integrity": "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==",
|
||||
"os": ["android"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@tailwindcss/oxide-darwin-arm64@4.2.0": {
|
||||
"integrity": "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@tailwindcss/oxide-darwin-x64@4.2.0": {
|
||||
"integrity": "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@tailwindcss/oxide-freebsd-x64@4.2.0": {
|
||||
"integrity": "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==",
|
||||
"os": ["freebsd"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0": {
|
||||
"integrity": "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm"]
|
||||
},
|
||||
"@tailwindcss/oxide-linux-arm64-gnu@4.2.0": {
|
||||
"integrity": "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@tailwindcss/oxide-linux-arm64-musl@4.2.0": {
|
||||
"integrity": "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@tailwindcss/oxide-linux-x64-gnu@4.2.0": {
|
||||
"integrity": "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@tailwindcss/oxide-linux-x64-musl@4.2.0": {
|
||||
"integrity": "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@tailwindcss/oxide-wasm32-wasi@4.2.0": {
|
||||
"integrity": "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==",
|
||||
"cpu": ["wasm32"]
|
||||
},
|
||||
"@tailwindcss/oxide-win32-arm64-msvc@4.2.0": {
|
||||
"integrity": "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@tailwindcss/oxide-win32-x64-msvc@4.2.0": {
|
||||
"integrity": "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@tailwindcss/oxide@4.2.0": {
|
||||
"integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==",
|
||||
"optionalDependencies": [
|
||||
"@tailwindcss/oxide-android-arm64",
|
||||
"@tailwindcss/oxide-darwin-arm64",
|
||||
"@tailwindcss/oxide-darwin-x64",
|
||||
"@tailwindcss/oxide-freebsd-x64",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu",
|
||||
"@tailwindcss/oxide-linux-arm64-musl",
|
||||
"@tailwindcss/oxide-linux-x64-gnu",
|
||||
"@tailwindcss/oxide-linux-x64-musl",
|
||||
"@tailwindcss/oxide-wasm32-wasi",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc",
|
||||
"@tailwindcss/oxide-win32-x64-msvc"
|
||||
]
|
||||
},
|
||||
"@tailwindcss/postcss@4.2.0": {
|
||||
"integrity": "sha512-u6YBacGpOm/ixPfKqfgrJEjMfrYmPD7gEFRoygS/hnQaRtV0VCBdpkx5Ouw9pnaLRwwlgGCuJw8xLpaR0hOrQg==",
|
||||
"dependencies": [
|
||||
"@alloc/quick-lru",
|
||||
"@tailwindcss/node",
|
||||
"@tailwindcss/oxide",
|
||||
"postcss",
|
||||
"tailwindcss"
|
||||
]
|
||||
},
|
||||
"@types/codemirror@5.60.8": {
|
||||
"integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
|
||||
"dependencies": [
|
||||
@@ -399,24 +221,6 @@
|
||||
"@types/ms@2.1.0": {
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
||||
},
|
||||
"@types/node@20.19.33": {
|
||||
"integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
|
||||
"dependencies": [
|
||||
"undici-types"
|
||||
]
|
||||
},
|
||||
"@types/react-dom@19.2.3_@types+react@19.2.14": {
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dependencies": [
|
||||
"@types/react"
|
||||
]
|
||||
},
|
||||
"@types/react@19.2.14": {
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dependencies": [
|
||||
"csstype"
|
||||
]
|
||||
},
|
||||
"@types/tern@0.23.9": {
|
||||
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
|
||||
"dependencies": [
|
||||
@@ -426,59 +230,21 @@
|
||||
"@types/unist@3.0.3": {
|
||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
|
||||
},
|
||||
"@wasm-tool/rollup-plugin-rust@3.1.4_binaryen@125.0.0": {
|
||||
"integrity": "sha512-XW3nk8yFPPc86jLXVEXkqQxsx9M6i+j5Boru5yapfS/+egK2IOYsuEOvM3am+/YrKczohKP45+cBKYkxSSGGzg==",
|
||||
"dependencies": [
|
||||
"@iarna/toml",
|
||||
"@rollup/pluginutils",
|
||||
"binaryen",
|
||||
"chalk",
|
||||
"glob",
|
||||
"node-fetch",
|
||||
"rimraf",
|
||||
"tar"
|
||||
]
|
||||
},
|
||||
"bail@2.0.2": {
|
||||
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
|
||||
},
|
||||
"balanced-match@4.0.3": {
|
||||
"integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g=="
|
||||
},
|
||||
"binaryen@125.0.0": {
|
||||
"integrity": "sha512-X7CUM9ZnwL/Ow++JH5AJKiemc82J7JyeryuPvXQdXBLcL/rqrC5KMUB1mHiORSolietH9sotvaOZlr6HSwPAlw==",
|
||||
"bin": true
|
||||
},
|
||||
"brace-expansion@5.0.2": {
|
||||
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
|
||||
"dependencies": [
|
||||
"balanced-match"
|
||||
]
|
||||
},
|
||||
"builtin-modules@5.0.0": {
|
||||
"integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg=="
|
||||
},
|
||||
"ccount@2.0.1": {
|
||||
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
|
||||
},
|
||||
"chalk@5.6.2": {
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="
|
||||
},
|
||||
"character-entities@2.0.2": {
|
||||
"integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="
|
||||
},
|
||||
"chownr@3.0.0": {
|
||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="
|
||||
},
|
||||
"crelt@1.0.6": {
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
},
|
||||
"csstype@3.2.3": {
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
|
||||
},
|
||||
"data-uri-to-buffer@4.0.1": {
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
|
||||
},
|
||||
"debug@4.4.3": {
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dependencies": [
|
||||
@@ -494,22 +260,12 @@
|
||||
"dequal@2.0.3": {
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
|
||||
},
|
||||
"detect-libc@2.1.2": {
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="
|
||||
},
|
||||
"devlop@1.1.0": {
|
||||
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
|
||||
"dependencies": [
|
||||
"dequal"
|
||||
]
|
||||
},
|
||||
"enhanced-resolve@5.19.0": {
|
||||
"integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
|
||||
"dependencies": [
|
||||
"graceful-fs",
|
||||
"tapable"
|
||||
]
|
||||
},
|
||||
"esbuild@0.27.3": {
|
||||
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
|
||||
"optionalDependencies": [
|
||||
@@ -546,18 +302,6 @@
|
||||
"escape-string-regexp@5.0.0": {
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
|
||||
},
|
||||
"estree-walker@2.0.2": {
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
},
|
||||
"eventsource-parser@3.0.6": {
|
||||
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="
|
||||
},
|
||||
"eventsource@4.1.0": {
|
||||
"integrity": "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==",
|
||||
"dependencies": [
|
||||
"eventsource-parser"
|
||||
]
|
||||
},
|
||||
"extend@3.0.2": {
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
@@ -567,129 +311,18 @@
|
||||
"format"
|
||||
]
|
||||
},
|
||||
"fetch-blob@3.2.0": {
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"dependencies": [
|
||||
"node-domexception",
|
||||
"web-streams-polyfill"
|
||||
]
|
||||
},
|
||||
"format@0.2.2": {
|
||||
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="
|
||||
},
|
||||
"formdata-polyfill@4.0.10": {
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dependencies": [
|
||||
"fetch-blob"
|
||||
]
|
||||
},
|
||||
"github-slugger@2.0.0": {
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
|
||||
},
|
||||
"glob@13.0.6": {
|
||||
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
||||
"dependencies": [
|
||||
"minimatch",
|
||||
"minipass",
|
||||
"path-scurry"
|
||||
]
|
||||
},
|
||||
"graceful-fs@4.2.11": {
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"is-plain-obj@4.1.0": {
|
||||
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="
|
||||
},
|
||||
"jiti@2.6.1": {
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"bin": true
|
||||
},
|
||||
"lightningcss-android-arm64@1.31.1": {
|
||||
"integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
|
||||
"os": ["android"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"lightningcss-darwin-arm64@1.31.1": {
|
||||
"integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"lightningcss-darwin-x64@1.31.1": {
|
||||
"integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"lightningcss-freebsd-x64@1.31.1": {
|
||||
"integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
|
||||
"os": ["freebsd"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"lightningcss-linux-arm-gnueabihf@1.31.1": {
|
||||
"integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm"]
|
||||
},
|
||||
"lightningcss-linux-arm64-gnu@1.31.1": {
|
||||
"integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"lightningcss-linux-arm64-musl@1.31.1": {
|
||||
"integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"lightningcss-linux-x64-gnu@1.31.1": {
|
||||
"integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"lightningcss-linux-x64-musl@1.31.1": {
|
||||
"integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"lightningcss-win32-arm64-msvc@1.31.1": {
|
||||
"integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"lightningcss-win32-x64-msvc@1.31.1": {
|
||||
"integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"lightningcss@1.31.1": {
|
||||
"integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
|
||||
"dependencies": [
|
||||
"detect-libc"
|
||||
],
|
||||
"optionalDependencies": [
|
||||
"lightningcss-android-arm64",
|
||||
"lightningcss-darwin-arm64",
|
||||
"lightningcss-darwin-x64",
|
||||
"lightningcss-freebsd-x64",
|
||||
"lightningcss-linux-arm-gnueabihf",
|
||||
"lightningcss-linux-arm64-gnu",
|
||||
"lightningcss-linux-arm64-musl",
|
||||
"lightningcss-linux-x64-gnu",
|
||||
"lightningcss-linux-x64-musl",
|
||||
"lightningcss-win32-arm64-msvc",
|
||||
"lightningcss-win32-x64-msvc"
|
||||
]
|
||||
},
|
||||
"longest-streak@3.1.0": {
|
||||
"integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="
|
||||
},
|
||||
"lru-cache@11.2.6": {
|
||||
"integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="
|
||||
},
|
||||
"magic-string@0.30.21": {
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"dependencies": [
|
||||
"@jridgewell/sourcemap-codec"
|
||||
]
|
||||
},
|
||||
"markdown-table@3.0.4": {
|
||||
"integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="
|
||||
},
|
||||
@@ -1070,46 +703,12 @@
|
||||
"micromark-util-types"
|
||||
]
|
||||
},
|
||||
"minimatch@10.2.2": {
|
||||
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
|
||||
"dependencies": [
|
||||
"brace-expansion"
|
||||
]
|
||||
},
|
||||
"minipass@7.1.3": {
|
||||
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="
|
||||
},
|
||||
"minizlib@3.1.0": {
|
||||
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
|
||||
"dependencies": [
|
||||
"minipass"
|
||||
]
|
||||
},
|
||||
"moment@2.29.4": {
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
},
|
||||
"ms@2.1.3": {
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"mutative@1.3.0": {
|
||||
"integrity": "sha512-8MJj6URmOZAV70dpFe1YnSppRTKC4DsMkXQiBDFayLcDI4ljGokHxmpqaBQuDWa4iAxWaJJ1PS8vAmbntjjKmQ=="
|
||||
},
|
||||
"nanoid@3.3.11": {
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"bin": true
|
||||
},
|
||||
"node-domexception@1.0.0": {
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": true
|
||||
},
|
||||
"node-fetch@3.3.2": {
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"dependencies": [
|
||||
"data-uri-to-buffer",
|
||||
"fetch-blob",
|
||||
"formdata-polyfill"
|
||||
]
|
||||
},
|
||||
"obsidian@1.12.2_@codemirror+state@6.5.0_@codemirror+view@6.38.6": {
|
||||
"integrity": "sha512-DGAzpt6vo+sMDET/o8Zj26Bj2hbHrFsNUU8TtnzCyerO/OHksMFQnU9QEnPHVsOMstt/WnHXfC56j2r1syObWg==",
|
||||
"dependencies": [
|
||||
@@ -1119,40 +718,6 @@
|
||||
"moment"
|
||||
]
|
||||
},
|
||||
"package-json-from-dist@1.0.1": {
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
||||
},
|
||||
"path-scurry@2.0.2": {
|
||||
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
||||
"dependencies": [
|
||||
"lru-cache",
|
||||
"minipass"
|
||||
]
|
||||
},
|
||||
"picocolors@1.1.1": {
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||
},
|
||||
"picomatch@4.0.3": {
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
|
||||
},
|
||||
"postcss@8.5.6": {
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dependencies": [
|
||||
"nanoid",
|
||||
"picocolors",
|
||||
"source-map-js"
|
||||
]
|
||||
},
|
||||
"react-dom@19.1.0_react@19.1.0": {
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"dependencies": [
|
||||
"react",
|
||||
"scheduler"
|
||||
]
|
||||
},
|
||||
"react@19.1.0": {
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="
|
||||
},
|
||||
"remark-frontmatter@5.0.0": {
|
||||
"integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==",
|
||||
"dependencies": [
|
||||
@@ -1190,52 +755,16 @@
|
||||
"unified"
|
||||
]
|
||||
},
|
||||
"rimraf@6.1.3": {
|
||||
"integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==",
|
||||
"dependencies": [
|
||||
"glob",
|
||||
"package-json-from-dist"
|
||||
],
|
||||
"bin": true
|
||||
},
|
||||
"scheduler@0.26.0": {
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
|
||||
},
|
||||
"source-map-js@1.2.1": {
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
||||
},
|
||||
"style-mod@4.1.3": {
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
|
||||
},
|
||||
"tailwindcss@4.2.0": {
|
||||
"integrity": "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q=="
|
||||
},
|
||||
"tapable@2.3.0": {
|
||||
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="
|
||||
},
|
||||
"tar@7.5.9": {
|
||||
"integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
|
||||
"dependencies": [
|
||||
"@isaacs/fs-minipass",
|
||||
"chownr",
|
||||
"minipass",
|
||||
"minizlib",
|
||||
"yallist"
|
||||
]
|
||||
},
|
||||
"trough@2.2.0": {
|
||||
"integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="
|
||||
},
|
||||
"tslib@2.8.1": {
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"typescript@5.9.3": {
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"bin": true
|
||||
},
|
||||
"undici-types@6.21.0": {
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
|
||||
},
|
||||
"unified@11.0.5": {
|
||||
"integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
|
||||
"dependencies": [
|
||||
@@ -1275,10 +804,6 @@
|
||||
"unist-util-visit-parents"
|
||||
]
|
||||
},
|
||||
"uuid@11.1.0": {
|
||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||
"bin": true
|
||||
},
|
||||
"vfile-message@4.0.3": {
|
||||
"integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
|
||||
"dependencies": [
|
||||
@@ -1296,45 +821,25 @@
|
||||
"w3c-keyname@2.2.8": {
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||
},
|
||||
"web-streams-polyfill@3.3.3": {
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="
|
||||
},
|
||||
"yallist@5.0.0": {
|
||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="
|
||||
},
|
||||
"zwitch@2.0.4": {
|
||||
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"jsr:@deno/esbuild-plugin@^1.2.1",
|
||||
"npm:@flowershow/remark-wiki-link@^3.4.0",
|
||||
"npm:@instantdb/admin@~0.21.3",
|
||||
"npm:@instantdb/react@~0.20.28",
|
||||
"npm:@react-oauth/google@~0.12.2",
|
||||
"npm:@tailwindcss/postcss@4",
|
||||
"npm:@types/node@20",
|
||||
"npm:@types/react-dom@19",
|
||||
"npm:@types/react@19",
|
||||
"npm:@wasm-tool/rollup-plugin-rust@^3.1.4",
|
||||
"npm:binaryen@125",
|
||||
"npm:builtin-modules@5",
|
||||
"npm:esbuild@~0.27.3",
|
||||
"npm:mdast@3",
|
||||
"npm:obsidian@^1.12.2",
|
||||
"npm:react-dom@19.1.0",
|
||||
"npm:react@19.1.0",
|
||||
"npm:remark-frontmatter@5",
|
||||
"npm:remark-gfm@^4.0.1",
|
||||
"npm:remark-parse@11",
|
||||
"npm:remark-stringify@11",
|
||||
"npm:tailwindcss@4",
|
||||
"npm:tslib@^2.6.2",
|
||||
"npm:typescript@^5.9.3",
|
||||
"npm:unified@^11.0.5",
|
||||
"npm:unist-util-visit@^5.1.0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
10
apps/obsidian-plugin/manifest.json
Normal file
10
apps/obsidian-plugin/manifest.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "mize",
|
||||
"name": "Mize",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"main": "main.js",
|
||||
"author": "c2vi",
|
||||
"isDesktopOnly": true,
|
||||
"description": "Turns Obsidian into a full Mize Explorer"
|
||||
}
|
||||
61
apps/obsidian-plugin/mod.rs
Normal file
61
apps/obsidian-plugin/mod.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use dioxus::prelude::*;
|
||||
use marts::ClientAuth;
|
||||
use mize::Mize;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use mize::{MizeResult, mize_part, MizePart, MizePartCreate, error::IntoMizeResultJsValue};
|
||||
use mize::platform::js::JsMize;
|
||||
|
||||
|
||||
// console_log macro
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn test_fn(mize: &mut JsMize) {
|
||||
let mize = mize.inner();
|
||||
console_log!("test fn...");
|
||||
|
||||
if let Err(e) = try_test_fn(mize) {
|
||||
console_log!("err: {:?}", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn try_test_fn(mize: &mut Mize) -> MizeResult<()> {
|
||||
//let auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
||||
//console_log!("secrets: {:?}", auth.secrets());
|
||||
|
||||
let parts = mize.parts.lock().unwrap();
|
||||
for part in parts.keys() {
|
||||
console_log!("part: {}", part)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn part_obsidian_init(mize: &mut JsMize, plugin: JsValue, app: JsValue) {
|
||||
let mut mize = mize.inner();
|
||||
if let Err(e) = obsidian_mize_main(&mut mize, plugin, app) {
|
||||
console_log!("Failed to start the obsidian mize part: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn obsidian_mize_main(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeResult<()> {
|
||||
|
||||
marts::part_obsidian(mize, plugin, app)?;
|
||||
marts::part_client_auth(mize)?;
|
||||
marts::spacetime(mize)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
173
apps/obsidian-plugin/plugin.ts
Normal file
173
apps/obsidian-plugin/plugin.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Plugin, ItemView, TFile, Modal } from "obsidian";
|
||||
import wasmBinary from "./rust_dist/marts_bg.wasm";
|
||||
import * as wasmNamespace from "./rust_dist/marts";
|
||||
import { c2vi_obsidian_canvas_patch } from "@ppc/marts";
|
||||
import { task_obsidian_otask_parse } from "@ppc/marts";
|
||||
|
||||
class Mize {
|
||||
jsInstance: wasmNamespace.JsMize;
|
||||
jsInstancePtr: number;
|
||||
wasmModule: any;
|
||||
|
||||
constructor(wasmModule: any, jsInstance: wasmNamespace.JsMize) {
|
||||
this.jsInstance = jsInstance
|
||||
this.jsInstancePtr = jsInstance.__wbg_ptr;
|
||||
this.wasmModule = wasmModule
|
||||
}
|
||||
|
||||
test() {
|
||||
this.wasmModule.test_fn(this.jsInstance);
|
||||
}
|
||||
|
||||
get_part_native(name: string) {
|
||||
if (name == "obsidian") {
|
||||
return this.obsidianPlugin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function mize_init() {
|
||||
|
||||
// init the wasm_bindgen module
|
||||
const wasmModule = await import("./rust_dist/marts.js");
|
||||
await wasmModule.default({ module_or_path: wasmBinary });
|
||||
wasmModule.initSync();
|
||||
const jsInstance = await wasmModule.new_js_instance(JSON.stringify({
|
||||
auth: {
|
||||
client: {
|
||||
issuer: "https://auth.ppc.social",
|
||||
client_id: "363768685120258051",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jsInstance.init();
|
||||
console.log("jsInstance: ", jsInstance)
|
||||
|
||||
let mize = new Mize(wasmModule, jsInstance);
|
||||
|
||||
console.log("jsInstance: ", jsInstance)
|
||||
console.log("mize.jsInstance: ", mize.jsInstance)
|
||||
|
||||
// set global mize
|
||||
window.mize = mize;
|
||||
console.log("window.mize.jsInstance: ", window.mize.jsInstance)
|
||||
|
||||
return mize;
|
||||
}
|
||||
|
||||
export default class MizeObsidianPlugin extends Plugin {
|
||||
async onload() {
|
||||
|
||||
let mize = await mize_init();
|
||||
console.log("mize.jsInstance: ", mize.jsInstance)
|
||||
mize.wasmModule.part_obsidian_init(mize.jsInstance, this, this.app);
|
||||
console.log("mize.jsInstance: ", mize.jsInstance)
|
||||
mize.obsidianPlugin = this;
|
||||
console.log("mize.jsInstance: ", mize.jsInstance)
|
||||
|
||||
// c2vi part which patches canvas movement
|
||||
c2vi_obsidian_canvas_patch(mize);
|
||||
|
||||
task_obsidian_otask_parse(mize);
|
||||
|
||||
this.registerView("test-view", (leaf) => new TestView(leaf, this));
|
||||
|
||||
this.addCommand({
|
||||
id: "mize-test-view",
|
||||
name: "Mize TestView",
|
||||
callback: async () => {
|
||||
const leaf = this.app.workspace.getLeaf(true);
|
||||
await leaf.setViewState({
|
||||
type: "test-view",
|
||||
active: true,
|
||||
});
|
||||
this.app.workspace.revealLeaf(leaf);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.app.workspace.detachLeavesOfType("ppc-app-view");
|
||||
}
|
||||
|
||||
showDeviceCode(code: string, verificationUri: string) {
|
||||
const modal = new Modal(this.app);
|
||||
|
||||
modal.onOpen = () => {
|
||||
const { contentEl } = modal;
|
||||
|
||||
contentEl.createEl("h2", { text: "Device Login" });
|
||||
|
||||
// URL
|
||||
contentEl.createEl("p", {
|
||||
text: "Open this URL:"
|
||||
});
|
||||
|
||||
const urlContainer = contentEl.createDiv({
|
||||
cls: "device-login-row"
|
||||
});
|
||||
|
||||
const urlInput = urlContainer.createEl("input", {
|
||||
type: "text",
|
||||
value: verificationUri,
|
||||
});
|
||||
|
||||
urlInput.readOnly = true;
|
||||
urlInput.style.width = "100%";
|
||||
|
||||
urlContainer.createEl("button", {
|
||||
text: "Copy"
|
||||
}).onclick = async () => {
|
||||
await navigator.clipboard.writeText(verificationUri);
|
||||
new Notice("URL copied to clipboard");
|
||||
};
|
||||
|
||||
// Code
|
||||
contentEl.createEl("p", {
|
||||
text: "Enter this code:"
|
||||
});
|
||||
|
||||
const codeInput = contentEl.createEl("input", {
|
||||
type: "text",
|
||||
value: code,
|
||||
});
|
||||
|
||||
codeInput.readOnly = true;
|
||||
codeInput.style.width = "100%";
|
||||
codeInput.select();
|
||||
};
|
||||
|
||||
modal.open();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class TestView extends ItemView {
|
||||
constructor(leaf: WorkspaceLeaf, plugin: PPCObsidianPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return "ppc-app-view";
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return "PPC App";
|
||||
}
|
||||
|
||||
override async onOpen() {
|
||||
this.root = document.createElement("div");
|
||||
this.root.id = "ppc-app-root";
|
||||
this.containerEl.children[1].appendChild(this.root);
|
||||
|
||||
this.plugin.mize.wasmModule.show_main_ui();
|
||||
}
|
||||
|
||||
override async onClose() {
|
||||
this.root.remove();
|
||||
}
|
||||
}
|
||||
3
apps/obsidian-plugin/src/main.rs
Normal file
3
apps/obsidian-plugin/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
13
apps/os/Cargo.toml
Normal file
13
apps/os/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mize-bin"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
mize = { workspace = true }
|
||||
clap = { version = "4.5.59", features = ["string"] }
|
||||
tracing = "0.1.44"
|
||||
|
||||
[[bin]]
|
||||
name = "mize"
|
||||
path = "./src/main.rs"
|
||||
161
apps/os/src/cli.rs
Normal file
161
apps/os/src/cli.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use clap::ArgMatches;
|
||||
use core::result::Result::Err;
|
||||
use core::unimplemented;
|
||||
use mize::flume::bounded;
|
||||
use mize::{instance, mize_err};
|
||||
use std::ffi::OsString;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tracing::debug;
|
||||
use mize::ciborium;
|
||||
|
||||
use mize::error::{IntoMizeResult, MizeError, MizeResult, MizeResultTrait};
|
||||
use mize::instance::subscription::Subscription;
|
||||
use mize::instance::subscription::Update;
|
||||
use mize::instance::Mize;
|
||||
use mize::item::{IntoItemData, ItemData};
|
||||
use mize::platform::os::config_from_cli_args;
|
||||
use mize::platform::os::fsstore::FileStore;
|
||||
|
||||
use crate::generated;
|
||||
|
||||
pub fn get(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
|
||||
let id = sub_matches
|
||||
.get_one::<String>("id")
|
||||
.ok_or(MizeError::new().msg("No id Argument specified"))?;
|
||||
|
||||
let raw = sub_matches.get_flag("raw");
|
||||
let data = instance.get_config(id)?;
|
||||
|
||||
if raw {
|
||||
print!("{}", data.value_string()?);
|
||||
} else {
|
||||
println!("{}", data);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn call(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
println!("call cmd not implemented");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
|
||||
let item = instance.new_item()?;
|
||||
|
||||
println!("id: {}", item.id());
|
||||
println!("with namespace: {}", item.id().namespace().as_real_string());
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn status(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
println!("not implemented");
|
||||
Err(mize_err!("not implemented"))
|
||||
}
|
||||
|
||||
pub fn info(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
println!("not implemented");
|
||||
Err(mize_err!("not implemented"))
|
||||
}
|
||||
|
||||
pub fn run(sub_matches: &ArgMatches, mize: &mut Mize) -> MizeResult<()> {
|
||||
match sub_matches.subcommand() {
|
||||
None => { // run a mize instance (does whatever it's config will say)
|
||||
mize.wait();
|
||||
}
|
||||
Some((runnable, sub_matches)) => { // run smth from a part. for now registered cli commands
|
||||
let mut ext_args: Vec<String> = sub_matches
|
||||
.get_many::<OsString>("")
|
||||
.unwrap_or_default()
|
||||
.map(|v| v.to_string_lossy().to_string())
|
||||
.collect();
|
||||
let mut ext_cmdline = vec![runnable.to_owned()];
|
||||
ext_cmdline.append(&mut ext_args);
|
||||
|
||||
let mut cli = generated::CliPart::get("cli")?;
|
||||
cli.run(ext_cmdline)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
let id = sub_matches
|
||||
.get_one::<String>("id")
|
||||
.ok_or(MizeError::new().msg("No id Argument specified"))?;
|
||||
|
||||
let value = sub_matches
|
||||
.get_one::<String>("value")
|
||||
.ok_or(MizeError::new().msg("No value Argument specified"))?;
|
||||
|
||||
instance.set_blocking(id, value.into_item_data())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
println!("not implemented");
|
||||
// let instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
// let id = sub_matches
|
||||
// .get_one::<String>("id")
|
||||
// .ok_or(MizeError::new().msg("No id Argument specified"))?;
|
||||
|
||||
// let item = instance.get(id)?;
|
||||
|
||||
// let (tx, rx) = bounded::<Update>(4);
|
||||
// let sub = Subscription::from_sender(tx);
|
||||
// instance.sub(id, sub)?;
|
||||
|
||||
// println!("item: {}", item.as_data_full()?);
|
||||
|
||||
// for update in rx {
|
||||
// println!("####### GOT UPDATE #######");
|
||||
// println!("item: {}", update.new_item()?.as_data_full()?);
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn format_cbor(sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
let mut input: Vec<u8> = Vec::new();
|
||||
let stdin = std::io::stdin();
|
||||
let mut handle = stdin.lock();
|
||||
//handle.read_to_end(&mut input);
|
||||
|
||||
let cbor_data = ciborium::from_reader(handle)?;
|
||||
let item_data = ItemData::from_cbor(cbor_data);
|
||||
|
||||
println!("{}", item_data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_from_part(cmd: &str, sub_matches: &ArgMatches, instance: &mut Mize) -> MizeResult<()> {
|
||||
let default = OsString::default();
|
||||
let mut sub_cmd_line: Vec<OsString> = sub_matches
|
||||
.get_many::<OsString>("")
|
||||
.unwrap()
|
||||
.map(|e| e.to_owned())
|
||||
.collect();
|
||||
sub_cmd_line.insert(0, OsString::from_str(cmd)?);
|
||||
|
||||
debug!("loading module '{}'", cmd);
|
||||
instance.load_part(cmd)?;
|
||||
|
||||
println!("running cli from part not implemented");
|
||||
//instance
|
||||
//.get_part(cmd)?
|
||||
//.run_cli(&instance, sub_cmd_line)
|
||||
//.ok_or(mize_err!(
|
||||
//"The Part '{cmd}' does not implement the run_cli() functionality"
|
||||
//))??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
70
apps/os/src/generated.rs
Normal file
70
apps/os/src/generated.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
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 run(&self, cmdline: Vec<String>) -> MizeResult<()> {
|
||||
let cmdline = cmdline.into_item_data();
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", cmdline)?;
|
||||
dyn_call(self.id.clone(), "run", args)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_command(&self, name: &str) -> MizeResult<()> {
|
||||
let name = name.into_item_data();
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", name)?;
|
||||
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 mut action_arg = ItemData::from_native(action)?;
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
// the clap::Command native stuff
|
||||
pub struct Command {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
pub struct ArgMatches {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
pub struct Action {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn new(name: &str) -> MizeResult<Command> {
|
||||
let name = name.into_item_data();
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", name)?;
|
||||
let mut mize = get_global_mize()?;
|
||||
let id = mize.id_new_dots("cli.Command")?;
|
||||
let data = dyn_call(id.clone(), "new", args)?;
|
||||
let native_id: u32 = data.get_path("_native")?.native_take()?;
|
||||
Ok(Command { native_id })
|
||||
}
|
||||
}
|
||||
177
apps/os/src/main.rs
Normal file
177
apps/os/src/main.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
#![allow(warnings)]
|
||||
|
||||
use clap::builder::OsStr;
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use clap::{ArgAction, ArgMatches};
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
|
||||
use mize::error::{MizeError, MizeResult};
|
||||
use mize::item::ItemData;
|
||||
use mize::platform::os::config_from_cli_args;
|
||||
use mize::platform::os::logging::init_logger;
|
||||
use mize::{Mize, instance, mize_err};
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
mod cli;
|
||||
// the generated module by hand. as we need the mize-bin to run generate
|
||||
mod generated;
|
||||
|
||||
static APPNAME: &str = "mize";
|
||||
|
||||
macro_rules! compile_in_parts {
|
||||
(
|
||||
$mize:ident,
|
||||
$(
|
||||
$name:literal : $part:path
|
||||
),+ $(,)?
|
||||
) => {
|
||||
let enable = $mize.get_config("enable")?;
|
||||
let mut enable_str = Vec::new();
|
||||
for name in enable.cbor().as_array().ok_or(mize_err!("config(enable) not a list"))? {
|
||||
enable_str.push(name.as_text().ok_or("part name not a str")?);
|
||||
}
|
||||
$(
|
||||
if enable_str.contains(&$name) {
|
||||
tracing::info!("enabling compiled_in_part: {}", $name);
|
||||
if let Err(e) = $part(&mut $mize) {
|
||||
print!("part '{}' failed with: ", $name);
|
||||
e.log();
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
fn main() {
|
||||
if let Err(e) = tried_main() {
|
||||
e.log();
|
||||
}
|
||||
}
|
||||
|
||||
fn tried_main() -> MizeResult<()> {
|
||||
let cli_matches = cli_matches();
|
||||
|
||||
// logging
|
||||
init_logger(Some(&cli_matches));
|
||||
|
||||
// init mize
|
||||
let mut mize = Mize::with_config(config_from_cli_args(&cli_matches)?)?;
|
||||
|
||||
// compiled in parts (which for now is all)
|
||||
/*
|
||||
compile_in_parts! {
|
||||
mize,
|
||||
"cli": marts::cli,
|
||||
"auth": marts::part_client_auth,
|
||||
"spacetime": marts::spacetime,
|
||||
//"matrix": marts::part_matrix,
|
||||
"ppc.server": marts::ppc::server::part_ppc_server,
|
||||
"c2vi": marts::part_c2vi,
|
||||
"sci.timetrack": marts::sci::timetrack::part_timetrack,
|
||||
};
|
||||
*/
|
||||
|
||||
|
||||
// match subcommand
|
||||
match cli_matches.subcommand() {
|
||||
Some(("run", sub_matches)) => cli::run(sub_matches, &mut mize),
|
||||
Some(("status", sub_matches)) => cli::status(sub_matches, &mut mize),
|
||||
Some(("info", sub_matches)) => cli::info(sub_matches, &mut mize),
|
||||
|
||||
Some(("get", sub_matches)) => cli::get(sub_matches, &mut mize),
|
||||
Some(("set", sub_matches)) => cli::set(sub_matches, &mut mize),
|
||||
Some(("show", sub_matches)) => cli::show(sub_matches, &mut mize),
|
||||
Some(("call", sub_matches)) => cli::call(sub_matches, &mut mize),
|
||||
Some(("create", sub_matches)) => cli::create(sub_matches, &mut mize),
|
||||
Some(("format-cbor", sub_matches)) => cli::format_cbor(sub_matches, &mut mize),
|
||||
|
||||
Some((cmd, sub_matches)) => cli::run_from_part(cmd, sub_matches, &mut mize),
|
||||
None => Err(MizeError::new()
|
||||
.msg("No subcommand was passed. use --help to list availavle comamnds.")),
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cli_matches() -> clap::ArgMatches {
|
||||
let main = Command::new(APPNAME)
|
||||
.version(crate_version!())
|
||||
.author("Sebastian Moser")
|
||||
.about("MiZe")
|
||||
.allow_external_subcommands(true)
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.long("verbose")
|
||||
.short('v')
|
||||
.action(ArgAction::Count)
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("log-level")
|
||||
.long("log-level")
|
||||
.value_name("LOGLEVEL")
|
||||
.help("set the log-level to one of OFF, ERROR, WARN, INFO, DEBUG, TRACE")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("log")
|
||||
.long("log")
|
||||
.help("log file to use")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("config")
|
||||
.long("config")
|
||||
.help("config file path")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("secrets")
|
||||
.long("secrets")
|
||||
.help("file path to store secrets at")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("opt")
|
||||
.short('o')
|
||||
.long("opt")
|
||||
.help("overwrite a config option")
|
||||
.global(true),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("get")
|
||||
.aliases(["g"])
|
||||
.arg(Arg::new("id").help("The id or path to get"))
|
||||
.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(
|
||||
Command::new("set")
|
||||
.aliases(["s"])
|
||||
.arg(Arg::new("id").help("The id or path to set"))
|
||||
.arg(Arg::new("value").help("The value to set the path to")),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("show")
|
||||
.aliases(["so"])
|
||||
.arg(Arg::new("id").help("The id or path to sub to and show")),
|
||||
)
|
||||
.subcommand(Command::new("call").aliases(["c"]))
|
||||
.subcommand(Command::new("create").aliases(["cr"]))
|
||||
.subcommand(Command::new("status").aliases(["st"]))
|
||||
.subcommand(Command::new("run").allow_external_subcommands(true))
|
||||
.subcommand(Command::new("format-cbor").aliases(["fc"]))
|
||||
.arg_required_else_help(true);
|
||||
|
||||
return main.get_matches();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
# Source code architecture
|
||||
|
||||
## 1. Core Mize Framework (`packages/mize/`)
|
||||
|
||||
**Architecture layers:**
|
||||
- `core/`: Platform-independent logic
|
||||
- `config/`: Configuration management
|
||||
- `error/`: Error handling
|
||||
- `id/`: Unique identifier system
|
||||
- `instance/`: Mize runtime instances
|
||||
- `item/`: Filesystem item abstractions
|
||||
- `memstore/`: In-memory storage
|
||||
- `proto/`: Protocol definitions
|
||||
- `types/`: Core type definitions
|
||||
|
||||
- `platform/`: Platform-specific implementations
|
||||
- `os/`: Native OS integration
|
||||
- `wasm/`: WebAssembly support
|
||||
- `bare/`: Bare metal targets
|
||||
- `jvm/`: Java Virtual Machine integration
|
||||
|
||||
### 2. Marts (`packages/marts/`)
|
||||
- **Language**: Rust + TypeScript
|
||||
- **Purpose**: Collection of Mize framework parts and utilities
|
||||
- **Key features**:
|
||||
- CLI tools
|
||||
- JavaScript/TypeScript integration
|
||||
- Habitica integration
|
||||
- Deno-based scripting support
|
||||
|
||||
## 3. MME - Mize Module Environment (`packages/mme/`)
|
||||
- **Language**: Rust
|
||||
- **Purpose**: Module execution environment
|
||||
- **Key features**:
|
||||
- Cross-platform module loading
|
||||
- Qt and Slint UI support
|
||||
- Web view integration (tao/wry)
|
||||
- Command system (comandr)
|
||||
|
||||
## 4. PPC - Platform Components (`packages/ppc/`)
|
||||
- **Language**: Rust + TypeScript
|
||||
- **Purpose**: Platform-specific implementations
|
||||
- **Targets**: Obsidian plugin, OS integration
|
||||
|
||||
## 5. Vic - Victor CLI (`packages/vic/`)
|
||||
- **Language**: Rust
|
||||
- **Purpose**: Command-line interface tool
|
||||
- **Features**: Build, run, test, and manage Mize applications
|
||||
@@ -1,30 +1,22 @@
|
||||
|
||||
[package]
|
||||
name = "mize"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0"
|
||||
description = 'A strongly typed "filesystem" for the age of connectivity.'
|
||||
repository = "https://github.com/c2vi/mize"
|
||||
|
||||
[package.metadata.wasm-pack.profile.dev]
|
||||
wasm-opt = false
|
||||
repository = "https://git.ppc.social/ppc/mize"
|
||||
|
||||
[lib]
|
||||
name = "mize"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[[bin]]
|
||||
name = "mize"
|
||||
path = "src/platform/os/main.rs"
|
||||
required-features = [ "target-os" ]
|
||||
crate-type = [ "cdylib", "rlib" ]
|
||||
|
||||
[features]
|
||||
default = [ "target-os" ]
|
||||
target-os= [ "clap", "home", "nix", "sysinfo", "ciborium/default", "tracing-subscriber", "async", "tracing-subscriber/env-filter", "libloading", "ciborium-io", "tracing-core", "tar", "flate2", "http_req" ]
|
||||
target-js = [ "wasm-bindgen", "console_error_panic_hook", "web-sys", "web-sys/Worker", "web-sys/Window", "web-sys/Request", "web-sys/RequestInit", "web-sys/RequestMode", "web-sys/Response", "web-sys/WorkerOptions", "web-sys/WorkerType", "serde-wasm-bindgen", "wasm-bindgen-futures", "wee_alloc"]
|
||||
async = ["tokio/net", "tokio", "tokio/rt-multi-thread", "tokio/io-util"]
|
||||
|
||||
target-os = [ "clap", "home", "nix", "sysinfo", "ciborium/default", "tracing-subscriber", "async", "tracing-subscriber/env-filter", "libloading", "ciborium-io", "tracing-core", "tar", "flate2", "http_req", "tokio/net", "tokio", "tokio/rt-multi-thread", "tokio/io-util", "cmd_lib" ]
|
||||
target-js = [ "wasm-bindgen", "console_error_panic_hook", "web-sys", "web-sys/Worker", "web-sys/Window", "web-sys/Request", "web-sys/RequestInit", "web-sys/RequestMode", "web-sys/Response", "web-sys/WorkerOptions", "web-sys/WorkerType", "serde-wasm-bindgen", "wasm-bindgen-futures", "wee_alloc", "ciborium/default" ]
|
||||
async = [ "tokio", ]
|
||||
|
||||
[dependencies]
|
||||
# core dependencies
|
||||
@@ -38,13 +30,10 @@ serde_json = { version = "1.0.117", optional = false }
|
||||
toml = { version = "0.8.13", features = ["parse"], optional = false }
|
||||
dyn-clone = "1.0.17"
|
||||
sha2 = "0.10.8"
|
||||
flume = "0.11.1"
|
||||
mize_macros = { path = "../ac_mize_macros" }
|
||||
flume = { version = "0.11.1", default-features = false, features = [ "async" ] }
|
||||
mize_macros = { path = "../macros" }
|
||||
async-trait = "0.1.89"
|
||||
|
||||
# builtin modules
|
||||
#mize_module_blob = { path = "./modules/modules/Blob" }
|
||||
|
||||
# wasm dependencies
|
||||
wasm-bindgen = { version = "0.2.84", optional = true }
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
@@ -60,38 +49,11 @@ daemonize = { version = "0.5.0", optional = true }
|
||||
nix = { version = "0.27.1", features = ["signal"], optional = true }
|
||||
sysinfo = { version = "0.30.12", optional = true }
|
||||
tracing-subscriber = { version = "0.3.18", optional = true }
|
||||
tokio = { version = "1.46.1", features = ["sync"], optional = true }
|
||||
tokio = { version = "1.46.1", features = ["sync", "rt" ], optional = true, default-features = false }
|
||||
libloading = { version = "0.8.5", optional = true }
|
||||
ciborium-io = { version = "0.2.2", optional = true }
|
||||
tracing-core = { version = "0.1.32", optional = true }
|
||||
tar = { version = "0.4.42", optional = true }
|
||||
flate2 = { version = "1.0.33", optional = true }
|
||||
http_req = { version = "0.12.0", default-features = false, features = [ "rust-tls" ], optional = true }
|
||||
#http_req = { git = "https://github.com/c2vi/http_req", optional = true, default-features = false, features = [ "native-tls" ] }
|
||||
|
||||
# bare dependencies
|
||||
|
||||
# unused dependencies
|
||||
axum = { version = "0.6.1", features = ["ws", "headers"], optional = true }
|
||||
axum-extra = { version = "0.4.2", features = ["spa"], optional = true }
|
||||
axum-macros = { version = "0.3.0", optional = true }
|
||||
#tokio-core = "0.1.18"
|
||||
#tokio-io = "0.1.13"
|
||||
|
||||
#tokio-stream = { version = "0.1.9", optional = true }
|
||||
#derive_more = "0.99.17"
|
||||
#futures-util = "0.3.24"
|
||||
#itertools = "0.10.5"
|
||||
#lazy_static = "1.4.0"
|
||||
#rmp-serde = "1.1.1"
|
||||
#rmpv = { version = "1.0.0", features = ["serde", "with-serde"] }
|
||||
#toml = "0.5.10"
|
||||
#tower-http = "0.3.5"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { version = "0.3.34" }
|
||||
|
||||
[profile.release]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
cmd_lib = { version = "2.0.1", optional = true }
|
||||
11
core/src/api.rs
Normal file
11
core/src/api.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn dyn_call(part_id: MizeId, method: &str, args: ItemData) -> MizeResult<ItemData> {
|
||||
let mut mize = get_global_mize()?;
|
||||
mize.dyn_call(part_id, method, args)
|
||||
}
|
||||
|
||||
pub fn get_config(name: &str) -> MizeResult<ItemData> {
|
||||
let mut mize = get_global_mize()?;
|
||||
mize.get_config(name)
|
||||
}
|
||||
255
core/src/c_api.rs
Normal file
255
core/src/c_api.rs
Normal file
@@ -0,0 +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;
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
287
core/src/config.rs
Normal file
287
core/src/config.rs
Normal file
@@ -0,0 +1,287 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::MizeResult;
|
||||
|
||||
use crate::get_global_mize;
|
||||
use crate::id::MizeId;
|
||||
use crate::item::{IntoItemData, ItemData};
|
||||
use crate::Mize;
|
||||
|
||||
pub type ConfigFun = Box<dyn Fn() -> MizeResult<ItemData> + Send + Sync + 'static>;
|
||||
pub type MizeFun = Box<dyn Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>;
|
||||
|
||||
pub struct ConfigOpt {
|
||||
pub name: String,
|
||||
pub val: Option<ItemData>,
|
||||
pub fun: Option<ConfigFun>,
|
||||
pub deps: Vec<String>,
|
||||
}
|
||||
|
||||
pub trait FromItemData {
|
||||
fn from_itemdata(data: ItemData) -> MizeResult<Self> where Self: Sized;
|
||||
}
|
||||
|
||||
impl Debug for ConfigOpt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}: {})", self.name, self.val.clone().unwrap_or(ItemData::new()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ConfigOptId {
|
||||
pub id: MizeId,
|
||||
}
|
||||
|
||||
impl ConfigOptId {
|
||||
pub fn default_val(self, val: ItemData) -> Self {
|
||||
let mize = get_global_mize().unwrap();
|
||||
// in generate mode: record the default value as metadata, skip normal op
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_opt("_default_val", self.id.clone(), Some(val)).ok();
|
||||
return self.clone();
|
||||
}
|
||||
let mut config_opts = mize.config_opts.lock().unwrap();
|
||||
let opt = config_opts.get_mut(&self.id).unwrap();
|
||||
opt.val = Some(val);
|
||||
self.clone()
|
||||
}
|
||||
pub fn fun(self, fun: ConfigFun) -> Self {
|
||||
let mize = get_global_mize().unwrap();
|
||||
// in generate mode: record as an opt with a fun, skip normal op
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_opt("_fun", self.id.clone(), None).ok();
|
||||
return self.clone();
|
||||
}
|
||||
let mut config_opts = mize.config_opts.lock().unwrap();
|
||||
let opt = config_opts.get_mut(&self.id).unwrap();
|
||||
opt.fun = Some(fun);
|
||||
self.clone()
|
||||
}
|
||||
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(self, name: &str, signature: &str, fun: F) -> ConfigOptId {
|
||||
let mize = get_global_mize().unwrap();
|
||||
let mut cur_part = self.id.clone();
|
||||
let id = cur_part.join(name).unwrap();
|
||||
// in generate mode: record method metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "method", signature).ok();
|
||||
return self.clone();
|
||||
}
|
||||
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);
|
||||
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
//pub trait ToItemDataHelper {
|
||||
//fn into_item_data(self) -> ItemData;
|
||||
//}
|
||||
|
||||
pub trait MizeAny {
|
||||
fn as_raw_ptr(&self) -> *const ();
|
||||
}
|
||||
|
||||
impl<T> MizeAny for T {
|
||||
fn as_raw_ptr(&self) -> *const () {
|
||||
self as *const Self as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 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 {
|
||||
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 opt metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_opt(name, id.clone(), None).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(),
|
||||
};
|
||||
opts.insert(id.clone(), opt);
|
||||
ConfigOptId { id }
|
||||
}
|
||||
|
||||
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, signature: &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 method metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "method", signature).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 effect<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 effect metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "effect", "").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 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 {
|
||||
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 thread metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "thread", "").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 }
|
||||
}
|
||||
|
||||
type CFun = unsafe extern "C" fn (*mut ItemData) -> *mut ItemData;
|
||||
|
||||
pub mod c_api {
|
||||
use std::ffi::c_char;
|
||||
|
||||
use crate::config::{CFun, ConfigOptId};
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub extern "C" fn opt(name: *const c_char) -> ConfigOptId {
|
||||
unsafe {
|
||||
let c_str = CStr::from_ptr(name);
|
||||
let string = c_str.to_string_lossy();
|
||||
super::opt(&string)
|
||||
}
|
||||
}
|
||||
pub extern "C" fn method(name: *const c_char, signature: *const c_char, c_fun: CFun) -> ConfigOptId {
|
||||
unsafe {
|
||||
let name_str = CStr::from_ptr(name).to_string_lossy();
|
||||
let sig_str = if signature.is_null() {
|
||||
String::new()
|
||||
} else {
|
||||
CStr::from_ptr(signature).to_string_lossy().into_owned()
|
||||
};
|
||||
super::method(&name_str, &sig_str, 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 effect(name: *const c_char, c_fun: CFun) -> ConfigOptId {
|
||||
unsafe {
|
||||
let c_str = CStr::from_ptr(name);
|
||||
let string = c_str.to_string_lossy();
|
||||
super::effect(&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 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 {
|
||||
unsafe {
|
||||
let c_str = CStr::from_ptr(name);
|
||||
let string = c_str.to_string_lossy();
|
||||
super::thread(&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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use colored::Colorize;
|
||||
#[cfg(feature = "target-js")]
|
||||
use wasm_bindgen::JsValue;
|
||||
use core::result::Result;
|
||||
use core::result::Result::Ok;
|
||||
use std::collections::HashMap;
|
||||
@@ -7,13 +9,13 @@ use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
use std::string::FromUtf8Error;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use crate::prelude::*;
|
||||
|
||||
//use crate::proto::MizeMessage;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! mize_err {
|
||||
($($arg:tt)*) => { MizeError::new().msg(format!( $($arg)*)) };
|
||||
($($arg:tt)*) => { $crate::error::MizeError::new().msg(format!( $($arg)*)) };
|
||||
}
|
||||
|
||||
pub type MizeResult<T> = Result<T, MizeError>;
|
||||
@@ -24,8 +26,8 @@ pub trait MizeResultTrait<T> {
|
||||
}
|
||||
|
||||
pub trait IntoMizeResult<T, S> {
|
||||
fn mize_result(self) -> MizeResult<T>;
|
||||
fn mize_result_msg(self, msg: S) -> MizeResult<T>
|
||||
fn mres(self) -> MizeResult<T>;
|
||||
fn mres_msg(self, msg: S) -> MizeResult<T>
|
||||
where
|
||||
S: std::fmt::Display;
|
||||
}
|
||||
@@ -125,16 +127,16 @@ impl MizeError {
|
||||
}
|
||||
|
||||
pub fn log(self) -> MizeError {
|
||||
error!("MizeError envountered!");
|
||||
println!("[ {} ] MizeError encountered!", "ERROR".red());
|
||||
|
||||
if let Some(ref location) = self.code_location {
|
||||
error!("[ {} ] {}", "LOCATION".yellow(), location);
|
||||
println!("[ {} ] {}", "LOCATION".yellow(), location);
|
||||
};
|
||||
|
||||
error!("[ {} ] {}", "BACKTRACE".yellow(), self.backtrace);
|
||||
println!("[ {} ] {}", "BACKTRACE".yellow(), self.backtrace);
|
||||
|
||||
for msg in &self.messages {
|
||||
error!("[ {} ] {}", "MSG".yellow(), msg);
|
||||
println!("[ {} ] {}", "MSG".yellow(), msg);
|
||||
}
|
||||
|
||||
self
|
||||
@@ -261,7 +263,7 @@ where
|
||||
E: std::fmt::Display,
|
||||
{
|
||||
#[track_caller]
|
||||
fn mize_result(self) -> MizeResult<T> {
|
||||
fn mres(self) -> MizeResult<T> {
|
||||
let caller_location = std::panic::Location::caller();
|
||||
|
||||
match self {
|
||||
@@ -274,7 +276,7 @@ where
|
||||
}
|
||||
}
|
||||
#[track_caller]
|
||||
fn mize_result_msg(self, msg: S) -> MizeResult<T>
|
||||
fn mres_msg(self, msg: S) -> MizeResult<T>
|
||||
where
|
||||
S: std::fmt::Display,
|
||||
{
|
||||
@@ -346,3 +348,20 @@ impl From<MizeError> for MizeErrorWhichImplementsError {
|
||||
}
|
||||
|
||||
impl std::error::Error for MizeErrorWhichImplementsError {}
|
||||
|
||||
// i hate rust sometimes.
|
||||
// why can't i impl IntoMizeResult for any type. and the extra concrete impl of it for JsValue overrides the more generic one.
|
||||
#[cfg(feature = "target-js")]
|
||||
pub trait IntoMizeResultJsValue<T> {
|
||||
fn mres(self) -> MizeResult<T>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-js")]
|
||||
impl<T> IntoMizeResultJsValue<T> for Result<T, JsValue> {
|
||||
fn mres(self) -> MizeResult<T> {
|
||||
match self {
|
||||
Ok(value) => Ok(value),
|
||||
Err(err) => Err(mize_err!("From JsValue: {:?}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,11 @@
|
||||
use core::fmt;
|
||||
use interner::{
|
||||
shared::{SharedPool, SharedString, SharedVecString},
|
||||
Pooled,
|
||||
shared::{SharedString, SharedVecString},
|
||||
};
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::path::Path;
|
||||
use std::{collections::binary_heap::Iter, fmt::write, hash::Hash};
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::error::{MizeError, MizeResult, MizeResultTrait};
|
||||
use crate::{
|
||||
instance::{store::Store, Mize},
|
||||
mize_err,
|
||||
};
|
||||
use crate::{get_global_mize, error::MizeResult};
|
||||
use crate::{instance::Mize, mize_err};
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct MizeId {
|
||||
@@ -38,6 +32,24 @@ pub trait IntoMizeId {
|
||||
}
|
||||
|
||||
impl MizeId {
|
||||
pub fn dot(&self) -> String {
|
||||
let path = self.path();
|
||||
let mut new_path = Vec::new();
|
||||
for el in path.iter() {
|
||||
new_path.push(el.to_owned());
|
||||
}
|
||||
new_path.join(".")
|
||||
}
|
||||
pub fn join(&mut self, to_add: &str) -> MizeResult<MizeId> {
|
||||
let path = self.path();
|
||||
let mut new_path = Vec::new();
|
||||
for el in path.iter() {
|
||||
new_path.push(el.to_owned());
|
||||
}
|
||||
new_path.push(to_add.to_owned());
|
||||
let mize = get_global_mize()?;
|
||||
Ok(new_path.to_mize_id(&mize)?)
|
||||
}
|
||||
pub fn store_part(&self) -> &str {
|
||||
self.path
|
||||
.iter()
|
||||
@@ -59,7 +71,7 @@ impl MizeId {
|
||||
|
||||
pub fn after_store_part(&self) -> Vec<String> {
|
||||
let tmp = self.path();
|
||||
let mut path_iter = tmp.into_iter();
|
||||
let mut path_iter = <Box<[std::string::String]> as Clone>::clone(&tmp).into_iter();
|
||||
path_iter.next();
|
||||
let new_path: Vec<String> = path_iter.map(|v| v.to_owned()).collect();
|
||||
|
||||
@@ -130,7 +142,7 @@ impl IntoMizeId for Vec<&str> {
|
||||
}
|
||||
}
|
||||
impl IntoMizeId for MizeId {
|
||||
fn to_mize_id(self, instance: &Mize) -> MizeResult<MizeId> {
|
||||
fn to_mize_id(self, _instance: &Mize) -> MizeResult<MizeId> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -41,9 +41,9 @@ pub fn value_raw_from_peer(item: &mut Item) -> MizeResult<ItemData> {
|
||||
let conn_id_str = id.nth_part(2)?;
|
||||
let conn_id: u64 = conn_id_str.parse()?;
|
||||
let tmp_path = id.path();
|
||||
let mut new_id_str = tmp_path.into_iter();
|
||||
let mut new_id_str = tmp_path.iter();
|
||||
let skipped = new_id_str.skip(4);
|
||||
let new_id = item.instance.new_id(skipped.collect::<Vec<&String>>())?;
|
||||
let new_id = item.instance.new_id(skipped.cloned().collect::<Vec<String>>())?;
|
||||
|
||||
let connection = item.instance.get_connection(conn_id)?;
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
use async_trait::async_trait;
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use flume::{bounded, unbounded, Receiver, Sender};
|
||||
use std::time::Duration;
|
||||
use flume::{bounded, unbounded, Sender};
|
||||
use interner::shared::{StringPool, VecStringPool};
|
||||
use mize_macros::mize_part;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::create_dir;
|
||||
use std::fs::File;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{thread, vec};
|
||||
use tracing::{debug, error, info, trace, warn, Instrument};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::ConfigOptNameAndMize;
|
||||
use crate::config::{gather_config, ConfigOpt};
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult, MizeResultTrait};
|
||||
use crate::set_global_mize;
|
||||
use crate::config::{ConfigOpt, ConfigOptId, MizeFun};
|
||||
use crate::error::{MizeError, MizeResult};
|
||||
use crate::id::{IntoMizeId, MizeId, Namespace};
|
||||
use crate::instance::store::Store;
|
||||
use crate::instance::subscription::Subscription;
|
||||
use crate::instance::updater::Operation;
|
||||
use crate::instance::updater::{updater_thread, updater_thread_async};
|
||||
use crate::instance::updater::updater_thread;
|
||||
use crate::item::{Item, ItemData};
|
||||
use crate::memstore::MemStore;
|
||||
use crate::mize_err;
|
||||
use crate::proto::MizeMessage;
|
||||
use crate::{mize_err, Module};
|
||||
use crate::error::IntoMizeResult;
|
||||
use crate::prelude::*;
|
||||
|
||||
use self::connection::{ConnListener, Connection};
|
||||
use self::updater::handle_operation;
|
||||
@@ -40,7 +37,6 @@ use core::future::Future;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
pub mod connection;
|
||||
pub mod module;
|
||||
pub mod msg_thread;
|
||||
pub mod store;
|
||||
pub mod subscription;
|
||||
@@ -51,7 +47,7 @@ mod tests;
|
||||
|
||||
static MSG_CHANNEL_SIZE: usize = 200;
|
||||
|
||||
static BUILD_TIME_CONFIG: &str = include_str!(std::env!("MIZE_BUILD_CONFIG"));
|
||||
static BUILD_TIME_CONFIG: &str = std::env!("MIZE_BUILD_CONFIG");
|
||||
|
||||
/// The Instance type is the heart of the mize system
|
||||
#[derive(Clone)]
|
||||
@@ -61,15 +57,18 @@ pub struct Mize {
|
||||
connections: Arc<Mutex<Vec<Connection>>>,
|
||||
next_con_id: Arc<Mutex<u64>>,
|
||||
subs: Arc<Mutex<HashMap<MizeId, Vec<Subscription>>>>,
|
||||
pub(crate) modules: Arc<Mutex<HashMap<String, Box<dyn Module + Sync + Send>>>>,
|
||||
pub(crate) id_pool: Arc<Mutex<VecStringPool>>,
|
||||
pub(crate) namespace_pool: Arc<Mutex<StringPool>>,
|
||||
|
||||
pub(crate) parts: Arc<Mutex<HashMap<&'static str, Option<Box<dyn MizePart + Sync + Send>>>>>,
|
||||
pub(crate) listeners: Arc<
|
||||
Mutex<HashMap<String, Arc<dyn Fn(MizeMessage, &mut Mize) -> MizeResult<()> + Send + Sync>>>,
|
||||
>,
|
||||
|
||||
part_names: Arc<Mutex<Vec<&'static str>>>,
|
||||
pub parts: Arc<Mutex<HashMap<MizeId, Option<Box<dyn MizePart + Sync + Send>>>>>,
|
||||
//pub(crate) platform: Arc<Mutex<Box<dyn MizePlatform>>>, // should we do that instead of all the #cfg(faturs=targt-smth) in lib.rs
|
||||
part_names: Arc<Mutex<Vec<&'static str>>>, // exists, so that we know the ordering in which parts were loaded
|
||||
|
||||
pub(crate) config_opts: Arc<Mutex<HashMap<String, ConfigOpt>>>,
|
||||
pub(crate) config_opts: Arc<Mutex<HashMap<MizeId, ConfigOpt>>>,
|
||||
|
||||
// the namespace the instance operates in
|
||||
pub(crate) namespace: Arc<Mutex<Namespace>>,
|
||||
@@ -83,133 +82,127 @@ pub struct Mize {
|
||||
give_msg_wait: Arc<Mutex<HashMap<MizeId, Vec<Sender<ItemData>>>>>,
|
||||
create_msg_wait: Arc<Mutex<Option<Sender<MizeId>>>>,
|
||||
|
||||
pub cur_eval_part: Arc<Mutex<Option<MizeId>>>,
|
||||
|
||||
pub native_values: Arc<Mutex<HashMap<u32, Box<dyn Any + Send + Sync>>>>,
|
||||
pub next_native_value_id: Arc<Mutex<u32>>,
|
||||
pub fun_values: Arc<Mutex<HashMap<MizeId, MizeFun>>>,
|
||||
pub next_fun_values_id: Arc<Mutex<u32>>,
|
||||
|
||||
// generate mode: when true, part_smth functions record metadata instead of executing
|
||||
pub generate_mode: Arc<RwLock<bool>>,
|
||||
// recorded metadata per part (keyed by part id) while in generate mode
|
||||
pub part_metadata: Arc<Mutex<HashMap<MizeId, PartMetadata>>>,
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) runtime: Arc<Mutex<Runtime>>,
|
||||
pub runtime: Arc<Mutex<Runtime>>,
|
||||
}
|
||||
|
||||
// the public Mize API
|
||||
/// Metadata recorded about a part while in generate mode
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PartMetadata {
|
||||
pub part_name: String,
|
||||
pub opts: Vec<OptMetadata>,
|
||||
pub methods: Vec<MethodMetadata>,
|
||||
pub effects: Vec<MethodMetadata>,
|
||||
pub applies: Vec<MethodMetadata>,
|
||||
pub threads: Vec<MethodMetadata>,
|
||||
pub natives: Vec<NativeMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OptMetadata {
|
||||
pub name: String,
|
||||
pub id: MizeId,
|
||||
pub default_val: Option<ItemData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MethodMetadata {
|
||||
pub name: String,
|
||||
pub id: MizeId,
|
||||
pub kind: String, // "method", "effect", "apply", "thread"
|
||||
/// Raw signature string, e.g. `"String -> ()"` or `"Command, Func -> ()"`.
|
||||
pub signature: String,
|
||||
/// Parsed argument type names (left of `->`).
|
||||
pub arg_types: Vec<String>,
|
||||
/// Parsed return type name (right of `->`). Empty means unspecified / ItemData.
|
||||
pub return_type: String,
|
||||
}
|
||||
|
||||
/// Parse a method signature like `"String -> ()"` or `"Command, Func -> ()"`.
|
||||
/// Returns `(arg_types, return_type)` where `return_type` is empty if no `->` was given.
|
||||
pub fn parse_method_signature(signature: &str) -> (Vec<String>, String) {
|
||||
let signature = signature.trim();
|
||||
if signature.is_empty() {
|
||||
return (Vec::new(), String::new());
|
||||
}
|
||||
let (args_part, ret_part) = match signature.split_once("->") {
|
||||
Some((a, r)) => (a.trim(), r.trim().to_string()),
|
||||
None => (signature, String::new()),
|
||||
};
|
||||
let arg_types = if args_part.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
args_part
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
};
|
||||
(arg_types, ret_part)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NativeMetadata {
|
||||
pub name: String,
|
||||
pub type_name: String,
|
||||
pub associated: Vec<String>,
|
||||
pub methods: Vec<String>,
|
||||
/// Signature strings parallel to `associated`.
|
||||
pub associated_sigs: Vec<String>,
|
||||
/// Signature strings parallel to `methods`.
|
||||
pub method_sigs: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct InstanceRef {
|
||||
inner: Arc<Mutex<Mize>>,
|
||||
}
|
||||
|
||||
pub struct PartRef {
|
||||
id: MizeId,
|
||||
}
|
||||
|
||||
impl PartRef {
|
||||
pub fn into_any(self) -> Box<dyn Any> {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl MizePart for PartRef {
|
||||
fn id(&self) -> MizeResult<MizeId> {
|
||||
Ok(self.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PartToNative {
|
||||
fn from_mize_id(id: MizeId) -> Self;
|
||||
}
|
||||
|
||||
pub struct InstanceAsync {
|
||||
inner: Arc<Mutex<Mize>>,
|
||||
}
|
||||
|
||||
pub struct DynMizePartGuard {
|
||||
pub mize: Mize,
|
||||
pub part: Option<Box<dyn MizePart + Send + Sync>>,
|
||||
}
|
||||
|
||||
pub struct MizePartGuard<T: MizePart + Send + Sync + 'static> {
|
||||
pub mize: Mize,
|
||||
pub part: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: MizePart + Send + Sync> Deref for MizePartGuard<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.part.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: MizePart + Send + Sync> DerefMut for MizePartGuard<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut *self.part.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: MizePart + Send + Sync> Drop for MizePartGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.part.is_none() {
|
||||
return;
|
||||
}
|
||||
let part = self.part.take().unwrap();
|
||||
self.mize.give_back_part(Box::new(part));
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DynMizePartGuard {
|
||||
type Target = dyn MizePart + Send + Sync;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.part.as_deref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DynMizePartGuard {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut *self.part.as_deref_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DynMizePartGuard {
|
||||
fn drop(&mut self) {
|
||||
if self.part.is_none() {
|
||||
return;
|
||||
}
|
||||
self.mize.give_back_part(self.part.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MizePartCreateGenerated: Sized {
|
||||
fn create_generated(mize: Mize) -> Self;
|
||||
}
|
||||
pub trait MizePartGenerated {
|
||||
fn name_generated(&self) -> &'static str;
|
||||
fn get_mize_generated(&mut self) -> &mut Mize;
|
||||
fn as_any_generated(&self) -> &dyn Any;
|
||||
fn as_any_mut_generated(&mut self) -> &mut dyn Any;
|
||||
fn into_any_generated(self: Box<Self>) -> Box<dyn Any>;
|
||||
}
|
||||
|
||||
pub trait MizePartCreate: MizePartCreateGenerated + MizePart + 'static {
|
||||
fn create(mize: &mut Mize) -> Box<dyn MizePart> {
|
||||
Box::new(Self::create_generated(mize.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait MizePart: MizePartGenerated {
|
||||
fn name(&self) -> &'static str {
|
||||
self.name_generated()
|
||||
}
|
||||
fn get_mize(&mut self) -> &mut Mize {
|
||||
self.get_mize_generated()
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self.as_any_generated()
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self.as_any_mut_generated()
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
self.into_any_generated()
|
||||
}
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn deps(&self) -> &'static [&'static str] {
|
||||
&[]
|
||||
}
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
()
|
||||
}
|
||||
async fn async_run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
async fn async_init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
pub trait MizePart {
|
||||
fn id(&self) -> MizeResult<MizeId>;
|
||||
}
|
||||
|
||||
impl Mize {
|
||||
pub fn empty() -> MizeResult<Mize> {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
|
||||
let guard = runtime.enter();
|
||||
std::mem::forget(guard);
|
||||
|
||||
let id_pool = Arc::new(Mutex::new(VecStringPool::default()));
|
||||
let namespace_pool_raw = StringPool::default();
|
||||
let connections = Arc::new(Mutex::new(Vec::new()));
|
||||
@@ -229,6 +222,7 @@ impl Mize {
|
||||
parts: Arc::new(Mutex::new(HashMap::new())),
|
||||
part_names: Arc::new(Mutex::new(Vec::new())),
|
||||
config_opts: Arc::new(Mutex::new(HashMap::new())),
|
||||
listeners: Arc::new(Mutex::new(HashMap::new())),
|
||||
connections,
|
||||
subs,
|
||||
id_pool,
|
||||
@@ -236,17 +230,21 @@ impl Mize {
|
||||
self_namespace,
|
||||
op_tx,
|
||||
namespace_pool: Arc::new(Mutex::new(namespace_pool_raw)),
|
||||
modules: Arc::new(Mutex::new(HashMap::new())),
|
||||
threads: Arc::new(Mutex::new(Vec::new())),
|
||||
next_thread_id: Arc::new(Mutex::new(0)),
|
||||
next_con_id: Arc::new(Mutex::new(1)),
|
||||
give_msg_wait,
|
||||
create_msg_wait,
|
||||
cur_eval_part: Arc::new(Mutex::new(None)),
|
||||
native_values: Arc::new(Mutex::new(HashMap::new())),
|
||||
next_native_value_id: Arc::new(Mutex::new(0)),
|
||||
fun_values: Arc::new(Mutex::new(HashMap::new())),
|
||||
next_fun_values_id: Arc::new(Mutex::new(0)),
|
||||
generate_mode: Arc::new(RwLock::new(false)),
|
||||
part_metadata: Arc::new(Mutex::new(HashMap::new())),
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
runtime: Arc::new(Mutex::new(
|
||||
Runtime::new().mize_result_msg("Could not create async runtime")?,
|
||||
)),
|
||||
runtime: Arc::new(Mutex::new(runtime)),
|
||||
};
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
@@ -282,7 +280,7 @@ impl Mize {
|
||||
console_log!("before loading build time config");
|
||||
|
||||
// load the config from build time
|
||||
let config = ItemData::from_toml(BUILD_TIME_CONFIG)?;
|
||||
let config = ItemData::from_json(BUILD_TIME_CONFIG.to_owned())?;
|
||||
instance.set_blocking("0", config);
|
||||
|
||||
#[cfg(feature = "target-wasm ")]
|
||||
@@ -306,60 +304,63 @@ impl Mize {
|
||||
return Ok(instance);
|
||||
}
|
||||
|
||||
pub fn get_part(&mut self, name: &str) -> MizeResult<DynMizePartGuard> {
|
||||
match self.parts.lock().unwrap().get_mut(name) {
|
||||
Some(part) => Ok(DynMizePartGuard {
|
||||
part: Some(part.take().unwrap()),
|
||||
mize: self.clone(),
|
||||
}),
|
||||
None => Err(mize_err!("Part '{name}' not found or currently taken")),
|
||||
}
|
||||
}
|
||||
pub fn has_part(&mut self, name: &str) -> bool {
|
||||
self.parts.lock().unwrap().contains_key(name)
|
||||
}
|
||||
pub fn add_part(&mut self, part: Box<dyn MizePart + Send + Sync>) -> MizeResult<()> {
|
||||
self.part_names.lock().unwrap().push(part.name());
|
||||
self.parts.lock().unwrap().insert(part.name(), Some(part));
|
||||
Ok(())
|
||||
}
|
||||
pub fn register_part(&mut self, part: Box<dyn MizePart + Send + Sync>) -> MizeResult<()> {
|
||||
self.part_names.lock().unwrap().push(part.name());
|
||||
self.parts.lock().unwrap().insert(part.name(), Some(part));
|
||||
Ok(())
|
||||
}
|
||||
fn part_names(&mut self) -> Vec<&'static str> {
|
||||
self.part_names.lock().unwrap().clone()
|
||||
pub fn id_new_dots(&mut self, name: &str) -> MizeResult<MizeId> {
|
||||
let parts = name.split(".").map(|v| v.to_owned()).collect::<Vec<String>>();
|
||||
self.new_id(parts)
|
||||
}
|
||||
|
||||
pub(crate) fn give_back_part(&mut self, part: Box<dyn MizePart + Send + Sync>) {
|
||||
self.parts.lock().unwrap().insert(part.name(), Some(part));
|
||||
pub fn get_part(&mut self, name: &str) -> MizeResult<PartRef> {
|
||||
let id = self.id_new_dots(name)?;
|
||||
Ok(PartRef { id })
|
||||
}
|
||||
pub fn has_part(&mut self, name: &str) -> MizeResult<bool> {
|
||||
let id = self.id_new_dots(name)?;
|
||||
Ok(self.config_opts.lock().unwrap().contains_key(&id))
|
||||
}
|
||||
pub fn add_listener(
|
||||
&mut self,
|
||||
name: &str,
|
||||
handler: Box<dyn Fn(MizeMessage, &mut Mize) -> MizeResult<()> + Send + Sync>,
|
||||
) -> MizeResult<()> {
|
||||
let name = name.to_owned();
|
||||
let mut listeners = self.listeners.lock().unwrap();
|
||||
listeners.insert(name, Arc::new(handler));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> MizeResult<()> {
|
||||
// gather options
|
||||
gather_config(self)?;
|
||||
|
||||
// set global mize
|
||||
set_global_mize(self.clone())?;
|
||||
|
||||
// platform specific init code
|
||||
crate::platform::any::instance_init(self)?;
|
||||
crate::platform::init(self)?;
|
||||
|
||||
// end of platform specific init code
|
||||
|
||||
// load the modules, ad specified in the load_modules config
|
||||
match self.get("0/config/load_modules")?.value_string() {
|
||||
Ok(modules_to_load) => {
|
||||
for module in modules_to_load.split(" ") {
|
||||
#[cfg(feature = "target-wasm ")]
|
||||
console_log!("loading module in Instance::init() ... {}", module);
|
||||
self.load_module(module)?;
|
||||
// load the parts, as specified in the parts config
|
||||
// Prefer `parts` when it is an array; otherwise `parts.load` (so `parts.workspaces` /
|
||||
// `parts.dist.cd` can coexist under a `parts` table in TOML).
|
||||
let parts_to_load = match self.get_config("parts") {
|
||||
Ok(data) if data.cbor().as_array().is_some() => Some(data),
|
||||
_ => self.get_config("parts.load").ok(),
|
||||
};
|
||||
if let Some(parts_to_load) = parts_to_load {
|
||||
for part_name in parts_to_load
|
||||
.cbor()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// no load_modules option is set
|
||||
self.load_part(part_name)?;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("INSTANCE INIT DONE");
|
||||
debug!("MIZE INIT DONE");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -367,13 +368,34 @@ impl Mize {
|
||||
trace!("[ {} ] Instance::with_config()", "CALL".yellow());
|
||||
trace!("config: {}", config);
|
||||
|
||||
let mut instance = Mize::empty()?;
|
||||
let mut mize = Mize::empty()?;
|
||||
|
||||
instance.set_blocking("0", config);
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let mut config = config.clone();
|
||||
for mut path in config.get_paths_recursive()? {
|
||||
if path.starts_with("/") {
|
||||
path.remove(0);
|
||||
}
|
||||
let path_split = path
|
||||
.split("/")
|
||||
.map(|s| s.to_owned())
|
||||
.collect::<Vec<String>>();
|
||||
let path_string = path_split.join(".");
|
||||
if let Ok(val) = config.get_path(path_split) {
|
||||
let opt = ConfigOpt {
|
||||
name: path.clone(),
|
||||
val: Some(val),
|
||||
fun: None,
|
||||
deps: Vec::new(),
|
||||
};
|
||||
opts.insert(mize.clone().id_new_dots(&path_string)?, opt);
|
||||
}
|
||||
}
|
||||
drop(opts);
|
||||
|
||||
instance.init()?;
|
||||
mize.init()?;
|
||||
|
||||
Ok(instance)
|
||||
Ok(mize)
|
||||
}
|
||||
|
||||
pub fn migrate_to_store(&self, new_store: Box<dyn Store>) -> MizeResult<()> {
|
||||
@@ -399,7 +421,7 @@ impl Mize {
|
||||
pub fn new_item(&self) -> MizeResult<Item> {
|
||||
if self.get_namespace()? != self.get_self_namespace()? {
|
||||
// need to send create msg and wait for it
|
||||
let mut connection = self.get_connection_by_ns(self.get_namespace()?)?;
|
||||
let connection = self.get_connection_by_ns(self.get_namespace()?)?;
|
||||
|
||||
let msg = MizeMessage::new_create(connection.id);
|
||||
|
||||
@@ -412,7 +434,6 @@ impl Mize {
|
||||
drop(msg_wait_inner);
|
||||
|
||||
let id = rx.recv()?;
|
||||
println!("new_item namespace: {:?}", id.namespace());
|
||||
|
||||
return Ok(Item::new(id, self));
|
||||
}
|
||||
@@ -515,29 +536,125 @@ impl Mize {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn load_module(&mut self, name: &str) -> MizeResult<()> {
|
||||
crate::platform::any::load_module(self, name, None)
|
||||
pub fn load_part(&mut self, name: &str) -> MizeResult<()> {
|
||||
tracing::debug!("loading part: {name}");
|
||||
crate::platform::load_part(name, None, None)
|
||||
}
|
||||
|
||||
pub fn fetch_module(&mut self, name: &str) -> MizeResult<String> {
|
||||
// platform specific way to fetch a module
|
||||
crate::platform::any::fetch_module(self, name)
|
||||
pub fn fetch_part(&mut self, name: &str) -> MizeResult<String> {
|
||||
// platform specific way to fetch a part
|
||||
crate::platform::fetch_part(self, name)
|
||||
}
|
||||
|
||||
pub fn load_module_at(&mut self, name: &str, path: String) -> MizeResult<()> {
|
||||
// platform specific init code
|
||||
crate::platform::any::load_module(self, name, Some(path))
|
||||
pub fn load_part_from_path(&mut self, name: &str, path: String) -> MizeResult<()> {
|
||||
crate::platform::load_part(name, Some(path), None)
|
||||
}
|
||||
|
||||
pub fn get_module(&mut self, name: &str) -> MizeResult<Box<dyn Module + Send + Sync>> {
|
||||
let inner = self.modules.lock()?;
|
||||
// ===== generate mode accessors =====
|
||||
|
||||
let module = inner
|
||||
.get(name)
|
||||
.ok_or(mize_err!("Couldn't get_module('{name}')"))?
|
||||
.clone_module();
|
||||
/// Enable or disable generate mode. When enabled, part_smth functions record
|
||||
/// metadata (methods, opts, natives) instead of performing their normal operations.
|
||||
pub fn set_generate_mode(&self, enabled: bool) -> MizeResult<()> {
|
||||
let mut mode = self.generate_mode.write().map_err(|_| mize_err!("generate_mode lock poisoned"))?;
|
||||
*mode = enabled;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
/// Check if generate mode is currently enabled.
|
||||
pub fn is_generate_mode(&self) -> MizeResult<bool> {
|
||||
let mode = self.generate_mode.read().map_err(|_| mize_err!("generate_mode lock poisoned"))?;
|
||||
Ok(*mode)
|
||||
}
|
||||
|
||||
/// Record an opt declaration while in generate mode.
|
||||
pub fn record_opt(&self, name: &str, id: MizeId, default_val: Option<ItemData>) -> MizeResult<()> {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone()
|
||||
.ok_or_else(|| mize_err!("no cur_eval_part set"))?;
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
let metadata = metadata_map.entry(cur_part.clone()).or_insert_with(|| PartMetadata {
|
||||
part_name: cur_part.dot(),
|
||||
..Default::default()
|
||||
});
|
||||
metadata.opts.push(OptMetadata {
|
||||
name: name.to_string(),
|
||||
id,
|
||||
default_val,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Record a method/effect/thread declaration while in generate mode.
|
||||
pub fn record_method(&self, name: &str, id: MizeId, kind: &str, signature: &str) -> MizeResult<()> {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone()
|
||||
.ok_or_else(|| mize_err!("no cur_eval_part set"))?;
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
let metadata = metadata_map.entry(cur_part.clone()).or_insert_with(|| PartMetadata {
|
||||
part_name: cur_part.dot(),
|
||||
..Default::default()
|
||||
});
|
||||
let (arg_types, return_type) = parse_method_signature(signature);
|
||||
let m = MethodMetadata {
|
||||
name: name.to_string(),
|
||||
id,
|
||||
kind: kind.to_string(),
|
||||
signature: signature.to_string(),
|
||||
arg_types,
|
||||
return_type,
|
||||
};
|
||||
match kind {
|
||||
"effect" => metadata.effects.push(m),
|
||||
"apply" => metadata.applies.push(m),
|
||||
"thread" => metadata.threads.push(m),
|
||||
_ => metadata.methods.push(m),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Record a native type inclusion while in generate mode.
|
||||
pub fn record_native(
|
||||
&self,
|
||||
name: &str,
|
||||
type_name: &str,
|
||||
associated: Vec<String>,
|
||||
methods: Vec<String>,
|
||||
associated_sigs: Vec<String>,
|
||||
method_sigs: Vec<String>,
|
||||
) -> MizeResult<()> {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone()
|
||||
.ok_or_else(|| mize_err!("no cur_eval_part set"))?;
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
let metadata = metadata_map.entry(cur_part.clone()).or_insert_with(|| PartMetadata {
|
||||
part_name: cur_part.dot(),
|
||||
..Default::default()
|
||||
});
|
||||
metadata.natives.push(NativeMetadata {
|
||||
name: name.to_string(),
|
||||
type_name: type_name.to_string(),
|
||||
associated,
|
||||
methods,
|
||||
associated_sigs,
|
||||
method_sigs,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get recorded metadata for a specific part.
|
||||
pub fn get_metadata(&self, part_id: &MizeId) -> MizeResult<Option<PartMetadata>> {
|
||||
let metadata_map = self.part_metadata.lock()?;
|
||||
Ok(metadata_map.get(part_id).cloned())
|
||||
}
|
||||
|
||||
/// Get all recorded metadata (for all parts).
|
||||
pub fn get_all_metadata(&self) -> MizeResult<Vec<PartMetadata>> {
|
||||
let metadata_map = self.part_metadata.lock()?;
|
||||
Ok(metadata_map.values().cloned().collect())
|
||||
}
|
||||
|
||||
/// Clear all recorded metadata.
|
||||
pub fn clear_metadata(&self) -> MizeResult<()> {
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
metadata_map.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn namespace_from_string(&self, ns_str: String) -> MizeResult<Namespace> {
|
||||
@@ -554,12 +671,12 @@ impl Mize {
|
||||
}
|
||||
|
||||
pub fn get_namespace(&self) -> MizeResult<Namespace> {
|
||||
let mut namespace_inner = self.namespace.lock()?;
|
||||
let namespace_inner = self.namespace.lock()?;
|
||||
return Ok(namespace_inner.clone());
|
||||
}
|
||||
|
||||
pub fn get_self_namespace(&self) -> MizeResult<Namespace> {
|
||||
let mut self_namespace_inner = self.self_namespace.lock()?;
|
||||
let self_namespace_inner = self.self_namespace.lock()?;
|
||||
return Ok(self_namespace_inner.clone());
|
||||
}
|
||||
|
||||
@@ -567,8 +684,8 @@ impl Mize {
|
||||
Ok(self.get_namespace()? == self.get_self_namespace()?)
|
||||
}
|
||||
|
||||
pub fn add_listener<T: ConnListener + 'static>(&mut self, listener: T) -> MizeResult<()> {
|
||||
let mut instance_clone = self.clone();
|
||||
pub fn add_conn_listener<T: ConnListener + 'static>(&mut self, listener: T) -> MizeResult<()> {
|
||||
let instance_clone = self.clone();
|
||||
self.spawn_background("some_listener", move || listener.listen(instance_clone));
|
||||
Ok(())
|
||||
}
|
||||
@@ -593,7 +710,7 @@ impl Mize {
|
||||
|
||||
let ns_of_peer_str = self
|
||||
.get(format!(
|
||||
"inst/con_by_id/{}/peer/0/config/namespace",
|
||||
"inst/con_by_id/{}/peer/self/config/namespace",
|
||||
conn_id
|
||||
))?
|
||||
.value_string()?;
|
||||
@@ -617,6 +734,21 @@ impl Mize {
|
||||
}
|
||||
|
||||
pub fn report_err(&self, err: MizeError) {
|
||||
#[cfg(feature = "target-js")] unsafe {
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
|
||||
}
|
||||
console_log!("reported_err: {:?}", err);
|
||||
}
|
||||
err.log();
|
||||
}
|
||||
|
||||
@@ -634,11 +766,10 @@ impl Mize {
|
||||
"Connection with id {} not known to instance",
|
||||
conn_id
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_connection(&self, conn_id: u64) -> MizeResult<Connection> {
|
||||
let mut conn_inner = self.connections.lock()?;
|
||||
let conn_inner = self.connections.lock()?;
|
||||
|
||||
for connection in conn_inner.iter() {
|
||||
if connection.id == conn_id {
|
||||
@@ -653,7 +784,7 @@ impl Mize {
|
||||
}
|
||||
|
||||
pub fn get_connection_by_ns(&self, ns: Namespace) -> MizeResult<Connection> {
|
||||
let mut conn_inner = self.connections.lock()?;
|
||||
let conn_inner = self.connections.lock()?;
|
||||
|
||||
for connection in conn_inner.iter() {
|
||||
if connection.ns == Some(ns.clone()) {
|
||||
@@ -677,11 +808,11 @@ impl Mize {
|
||||
let mut next_thread_id = self.next_thread_id.lock()?;
|
||||
|
||||
let my_thread_id_no_mutex_guard = *next_thread_id;
|
||||
let thread_mutex = self.threads.clone();
|
||||
let _thread_mutex = self.threads.clone();
|
||||
let name_to_move = name.to_owned();
|
||||
let to_spawn = move || -> MizeResult<()> {
|
||||
debug!("spawning thread: {}", name_to_move);
|
||||
let my_thread_id = my_thread_id_no_mutex_guard;
|
||||
let _my_thread_id = my_thread_id_no_mutex_guard;
|
||||
|
||||
if let Err(err) = func() {
|
||||
mize_clone.report_err(err);
|
||||
@@ -727,17 +858,23 @@ impl Mize {
|
||||
) -> MizeResult<()> {
|
||||
let mut threads_inner = self.threads.lock()?;
|
||||
let mut next_thread_id = self.next_thread_id.lock()?;
|
||||
let tokio_handle = tokio::runtime::Handle::current();
|
||||
|
||||
let my_thread_id_no_mutex_guard = *next_thread_id;
|
||||
let thread_mutex = self.threads.clone();
|
||||
let _thread_mutex = self.threads.clone();
|
||||
let name_to_move = name.to_owned();
|
||||
let to_spawn = move || -> MizeResult<()> {
|
||||
let to_spawn = move || -> () {
|
||||
debug!("spawning thread: {}", name_to_move);
|
||||
let my_thread_id = my_thread_id_no_mutex_guard;
|
||||
let guard = tokio_handle.enter();
|
||||
std::mem::forget(guard);
|
||||
let _my_thread_id = my_thread_id_no_mutex_guard;
|
||||
|
||||
func()?;
|
||||
let res = func();
|
||||
if let Err(e) = res {
|
||||
e.log();
|
||||
}
|
||||
|
||||
let mut threads_inner = thread_mutex.lock()?;
|
||||
//let mut threads_inner = thread_mutex.lock()?;
|
||||
/*
|
||||
*threads_inner = threads_inner
|
||||
.clone()
|
||||
@@ -749,7 +886,6 @@ impl Mize {
|
||||
.collect();
|
||||
*/
|
||||
debug!("thread '{}' stopped", name_to_move);
|
||||
Ok(())
|
||||
};
|
||||
|
||||
*next_thread_id += 1;
|
||||
@@ -759,7 +895,7 @@ impl Mize {
|
||||
|
||||
threads_inner.push((*next_thread_id, name.to_owned(), None));
|
||||
|
||||
#[cfg(feature = "target-wasm ")]
|
||||
#[cfg(feature = "target-js")]
|
||||
{
|
||||
//console_log!("in instance::spawn with wasm target")
|
||||
}
|
||||
@@ -803,8 +939,8 @@ impl Mize {
|
||||
let mut next_thread_id = self.next_thread_id.lock()?;
|
||||
|
||||
threads_inner.push((*next_thread_id, name.to_owned(), None));
|
||||
let runtime_inner = self.runtime.lock().unwrap();
|
||||
runtime_inner.spawn(func);
|
||||
let runtime = tokio::runtime::Handle::current();
|
||||
runtime.spawn(func);
|
||||
|
||||
*next_thread_id += 1;
|
||||
|
||||
@@ -813,9 +949,7 @@ impl Mize {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub fn async_get_handle(&self) -> Handle {
|
||||
let runtime_inner = self.runtime.lock().unwrap();
|
||||
let handle = runtime_inner.handle().to_owned();
|
||||
handle
|
||||
tokio::runtime::Handle::current()
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -836,15 +970,13 @@ impl Mize {
|
||||
.expect("mutex lock failed in spawn_async_blocking");
|
||||
|
||||
threads_inner.push((*next_thread_id, name.to_owned(), None));
|
||||
let runtime_inner = self.runtime.lock().unwrap();
|
||||
let handle = runtime_inner.handle().to_owned();
|
||||
let runtime = tokio::runtime::Handle::current();
|
||||
*next_thread_id += 1;
|
||||
|
||||
drop(runtime_inner);
|
||||
drop(threads_inner);
|
||||
drop(next_thread_id);
|
||||
|
||||
let result = handle.block_on(func);
|
||||
let result = runtime.block_on(func);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -852,7 +984,7 @@ impl Mize {
|
||||
pub fn wait(&self) {
|
||||
info!("Instance main thread waiting");
|
||||
loop {
|
||||
thread::sleep_ms(10000000)
|
||||
thread::sleep(Duration::from_secs(100000))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,108 +995,83 @@ impl Mize {
|
||||
pub fn get_config(&mut self, name: &str) -> MizeResult<ItemData> {
|
||||
let mut config_opts = self.config_opts.lock().unwrap();
|
||||
|
||||
let opt = config_opts
|
||||
.get_mut(name)
|
||||
.ok_or_else(|| mize_err!("Config opt for {name} not in config_opts"))?;
|
||||
let mut id;
|
||||
let mut split = name.split(".");
|
||||
if split.nth(0).unwrap() == "self" {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone().ok_or(mize_err!("get_config called but no curr eval part set"))?;
|
||||
id = cur_part.clone();
|
||||
for el in split {
|
||||
id = id.join(el)?;
|
||||
}
|
||||
} else {
|
||||
id = self.clone().id_new_dots(name)?;
|
||||
}
|
||||
|
||||
let mut opt = config_opts
|
||||
.get_mut(&id)
|
||||
.ok_or_else(|| mize_err!("Config opt for {id} not in config_opts"))?;
|
||||
|
||||
// get the cached value
|
||||
if let Some(val) = opt.val.clone() {
|
||||
println!("get_config: {name} has cached value: {val}");
|
||||
return Ok(val);
|
||||
}
|
||||
|
||||
// Evaluate the thunk
|
||||
let thunk = opt
|
||||
.thunk
|
||||
.take()
|
||||
.ok_or_else(|| mize_err!("ConfigOpt {name} does not have a thunk"))?;
|
||||
let result = thunk();
|
||||
|
||||
// Cache the result
|
||||
opt.val = Some(result.clone());
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
pub fn new_opt(&mut self, name: &str) -> ConfigOptNameAndMize {
|
||||
// in case it is not cached run the fun if it exists
|
||||
if opt.fun.is_some() {
|
||||
let fun = opt.fun.take().unwrap();
|
||||
drop(config_opts);
|
||||
let res = fun()?;
|
||||
let mut config_opts = self.config_opts.lock().unwrap();
|
||||
match config_opts.get_mut(name) {
|
||||
Some(opt) => {}
|
||||
None => {
|
||||
let opt = ConfigOpt {
|
||||
name: name.to_owned(),
|
||||
val: None,
|
||||
thunk: None,
|
||||
};
|
||||
config_opts.insert(name.to_string(), opt);
|
||||
}
|
||||
}
|
||||
ConfigOptNameAndMize {
|
||||
name: name.to_string(),
|
||||
mize: self.clone(),
|
||||
}
|
||||
let mut opt = config_opts.get_mut(&id).unwrap();
|
||||
opt.fun = Some(fun);
|
||||
opt.val = Some(res.clone());
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> MizeResult<()> {
|
||||
let part_names = self.part_names();
|
||||
for part_name in part_names {
|
||||
let mut part = self.get_part(part_name)?;
|
||||
//println!("running part {part_name}");
|
||||
part.run(&mut self.clone())?;
|
||||
Err(mize_err!("ConfigOpt {id} has no fun and no cached value"))
|
||||
}
|
||||
let mut threads_inner = self.threads.lock().unwrap();
|
||||
for (id, name, handle) in threads_inner.drain(..) {
|
||||
if let Some(handle) = handle {
|
||||
handle.join().unwrap()?;
|
||||
|
||||
pub fn dyn_call(&mut self, part_id: MizeId, method: &str, args: ItemData) -> MizeResult<ItemData> {
|
||||
debug!("dyn call {}/{} with args: {}", part_id, method, args);
|
||||
let mut fun_values = self.fun_values.lock()?;
|
||||
|
||||
let id = part_id.clone().join(method)?;
|
||||
//let part = opts.get_mut(&id).ok_or(mize_err!("part {} is not loaded", part_id.dot()))?;
|
||||
let fun = fun_values.remove(&id).ok_or(mize_err!("fun value for {id} not found"))?;
|
||||
drop(fun_values);
|
||||
|
||||
let calling_part = self.clone().cur_eval_part()?;
|
||||
self.clone().set_cur_eval_part(Some(part_id));
|
||||
let res = fun(args);
|
||||
self.clone().set_cur_eval_part(calling_part);
|
||||
let mut fun_values = self.fun_values.lock()?;
|
||||
fun_values.insert(id, fun);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn cur_eval_part(&mut self) -> MizeResult<Option<MizeId>> {
|
||||
Ok(self.cur_eval_part.lock()?.clone())
|
||||
}
|
||||
|
||||
pub fn set_cur_eval_part(&mut self, part: Option<MizeId>) -> MizeResult<()> {
|
||||
let mut guard = self.cur_eval_part.lock()?;
|
||||
*guard = part;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_part_native<T: MizePart + Send + Sync + 'static>(
|
||||
pub fn get_part_gen<T: MizePart + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
) -> MizeResult<MizePartGuard<T>> {
|
||||
let mut dyn_guard = self.get_part(name)?;
|
||||
let part = dyn_guard.part.take().unwrap();
|
||||
drop(dyn_guard);
|
||||
let concrete_part = part.into_any().downcast::<T>().unwrap();
|
||||
Ok(MizePartGuard {
|
||||
mize: self.clone(),
|
||||
part: Some(*concrete_part),
|
||||
})
|
||||
) -> MizeResult<T> {
|
||||
let dyn_part = self.get_part(name)?;
|
||||
let concrete_part = dyn_part
|
||||
.into_any()
|
||||
.downcast::<T>()
|
||||
.map_err(|_| mize_err!("failed to downcast"))?;
|
||||
Ok(*concrete_part)
|
||||
}
|
||||
pub fn add_name_only_part(&mut self, name: &'static str) {
|
||||
let part = NameOnlyPart {
|
||||
mize: self.clone(),
|
||||
name,
|
||||
};
|
||||
self.add_part(Box::new(part));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NameOnlyPart {
|
||||
mize: Mize,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl MizePart for NameOnlyPart {}
|
||||
impl MizePartGenerated for NameOnlyPart {
|
||||
fn as_any_generated(&self) -> &dyn Any {
|
||||
self.as_any()
|
||||
}
|
||||
fn as_any_mut_generated(&mut self) -> &mut dyn Any {
|
||||
self.as_any_mut()
|
||||
}
|
||||
fn get_mize_generated(&mut self) -> &mut Mize {
|
||||
&mut self.mize
|
||||
}
|
||||
fn into_any_generated(self: Box<Self>) -> Box<dyn Any> {
|
||||
self.into_any()
|
||||
}
|
||||
fn name_generated(&self) -> &'static str {
|
||||
self.name
|
||||
pub fn id_path(&self, id: &MizeId) -> MizeResult<String> {
|
||||
Ok(id.path().join("/"))
|
||||
}
|
||||
}
|
||||
|
||||
2
core/src/instance/msg_thread.rs
Normal file
2
core/src/instance/msg_thread.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
// Message thread handling for the Mize instance.
|
||||
// This module is reserved for future message threading functionality.
|
||||
@@ -2,13 +2,13 @@ use core::fmt;
|
||||
use flume::Sender;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::error::MizeResult;
|
||||
use crate::id::MizeId;
|
||||
use crate::instance::connection::Connection;
|
||||
use crate::item::Item;
|
||||
use crate::proto::MizeMessage;
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::Mize;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use tracing_subscriber::registry::Data;
|
||||
|
||||
use crate::item::IntoItemData;
|
||||
|
||||
use super::*;
|
||||
@@ -43,21 +41,21 @@ fn test_sort_keys() -> MizeResult<()> {
|
||||
fn test_get_module_hash() -> MizeResult<()> {
|
||||
let mut instance = Mize::empty()?;
|
||||
|
||||
use crate::platform::os::get_module_hash;
|
||||
use crate::platform::os::get_part_hash;
|
||||
let selector_toml = r#"
|
||||
mize_version = "0.0.0"
|
||||
system = "x86_64-unknown-linux-gnu"
|
||||
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", options)?;
|
||||
|
||||
let (hash, selector) = get_module_hash(&mut instance, "String", ItemData::new())?;
|
||||
println!("hash: {}", hash);
|
||||
println!("selector: {}", selector);
|
||||
|
||||
test_println!("hash: {}", hash);
|
||||
test_println!("selector: {}", selector);
|
||||
|
||||
assert_eq!(hash, "e96edf60216688314efc263c1aa8d6ce".to_owned());
|
||||
assert_eq!(hash, "227b0216f9b7a324a621ff773c5241c9".to_owned());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -70,7 +68,7 @@ fn test_set_store_path() -> MizeResult<()> {
|
||||
|
||||
let config = instance.get("0/config")?.as_data_full()?;
|
||||
|
||||
test_println!("data at 0/config: {}", config);
|
||||
println!("data at 0/config: {}", config);
|
||||
|
||||
instance.set_blocking("0/config/store_path", store_path.clone().into_item_data())?;
|
||||
|
||||
@@ -252,12 +250,3 @@ fn test_item_merge() -> MizeResult<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
#[should_panic(expected = "correct panic")]
|
||||
fn test_cant_set_non_existent_item() -> () {
|
||||
let instance = Instance::empty().expect("wrong panic");
|
||||
instance.set_blocking("5", "hello world".into_item_data()).expect("correct panic");
|
||||
}
|
||||
*/
|
||||
@@ -1,11 +1,11 @@
|
||||
use flume::Receiver;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, trace, warn};
|
||||
|
||||
use crate::error::{MizeError, MizeResult, MizeResultTrait};
|
||||
use crate::id::MizeId;
|
||||
use crate::mize_err;
|
||||
use crate::prelude::*;
|
||||
use crate::proto::{MessageCmd, MizeMessage};
|
||||
use crate::{instance::Mize, item::ItemData};
|
||||
|
||||
@@ -215,6 +215,19 @@ fn handle_msg(msg: &mut MizeMessage, instance: &Mize) -> MizeResult<()> {
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
MessageCmd::Custom(type_val) => {
|
||||
let listeners = instance.listeners.lock()?;
|
||||
let type_str = type_val.value_string()?;
|
||||
let msg = msg.clone();
|
||||
let mut mize = instance.clone();
|
||||
println!("type_str: {}", type_str);
|
||||
println!("listeners: {:?}", listeners.keys());
|
||||
if let Some(listener) = listeners.get(&type_str) {
|
||||
let listener = listener.clone();
|
||||
drop(listeners);
|
||||
listener(msg, &mut mize)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(mize_err!("got a message, that is not handeled"));
|
||||
}
|
||||
1686
core/src/item.rs
Normal file
1686
core/src/item.rs
Normal file
File diff suppressed because it is too large
Load Diff
308
core/src/lib.rs
Normal file
308
core/src/lib.rs
Normal file
@@ -0,0 +1,308 @@
|
||||
#![allow(warnings)]
|
||||
#![feature(str_as_str)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::format;
|
||||
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! out {
|
||||
($($arg:tt)*) => {
|
||||
// format_args! handles string interpolation safely at compile time
|
||||
if let Err(e) = $crate::write_to_mize_stdout(format_args!("{}\n", format_args!($($arg)*))) {
|
||||
// Choose your error handling behavior here (panic, print to stderr, or ignore)
|
||||
eprintln!("out! macro failed to write!!");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write_to_mize_stdout(args: std::fmt::Arguments) -> MizeResult<()> {
|
||||
print!("{}", args);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mize Shell (msh) macro - wraps command execution with nixpkgs executable resolution.
|
||||
///
|
||||
/// If the executable is given as a bare name (identifier), it is resolved via
|
||||
/// [`msh_get_nix_bin`] to a nixpkgs store path. If it is given as a string literal
|
||||
/// containing `/`, it is treated as a path and used as-is.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// msh!(git status)?; // resolves `git` from nixpkgs
|
||||
/// msh!(curl https://example.com)?;
|
||||
/// msh!("/usr/bin/git" status)?; // uses path directly
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! msh {
|
||||
// First token is a string literal — check if it's a path at runtime
|
||||
($cmd:literal $($rest:tt)*) => {{
|
||||
let cmd_str: &str = $cmd;
|
||||
let resolved: String = if cmd_str.contains('/') {
|
||||
cmd_str.to_string()
|
||||
} else {
|
||||
$crate::msh_get_nix_bin(cmd_str)
|
||||
};
|
||||
let mut cmd = ::std::process::Command::new(resolved);
|
||||
$(cmd.arg(::std::stringify!($rest));)*
|
||||
let status = cmd.status()?;
|
||||
if status.success() {
|
||||
::std::result::Result::Ok(())
|
||||
} else {
|
||||
::std::result::Result::Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
::std::format!("msh: command exited with {}", status),
|
||||
))
|
||||
}
|
||||
}};
|
||||
// First token is an identifier — always resolve via nixpkgs
|
||||
($cmd:ident $($rest:tt)*) => {{
|
||||
let resolved: String = $crate::msh_get_nix_bin(::std::stringify!($cmd));
|
||||
let mut cmd = ::std::process::Command::new(resolved);
|
||||
$(cmd.arg(::std::stringify!($rest));)*
|
||||
let status = cmd.status()?;
|
||||
if status.success() {
|
||||
::std::result::Result::Ok(())
|
||||
} else {
|
||||
::std::result::Result::Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
::std::format!("msh: command exited with {}", status),
|
||||
))
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::*;
|
||||
pub use instance::Mize;
|
||||
pub use id::*;
|
||||
pub use error::MizeError;
|
||||
pub use crate::mize_err;
|
||||
pub use error::MizeResult;
|
||||
pub use error::IntoMizeResult;
|
||||
pub use item::{Item, ItemData, IntoItemData};
|
||||
pub use config::*;
|
||||
pub use api::*;
|
||||
pub use mize_macros::*;
|
||||
pub use crate::tracing::{trace, warn, debug, info, error};
|
||||
}
|
||||
pub use flume;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::runtime::Runtime;
|
||||
pub use tracing;
|
||||
|
||||
|
||||
|
||||
// the core part has the code that can run on any platform
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod id;
|
||||
pub mod instance;
|
||||
pub mod item;
|
||||
pub mod memstore;
|
||||
pub mod proto;
|
||||
pub mod types;
|
||||
pub mod c_api;
|
||||
pub mod api;
|
||||
|
||||
pub use async_trait::async_trait;
|
||||
pub use ciborium;
|
||||
pub use error::MizeError;
|
||||
pub use error::MizeResult;
|
||||
pub use instance::Mize;
|
||||
pub use instance::{
|
||||
MizePart, PartMetadata, OptMetadata, MethodMetadata, NativeMetadata, parse_method_signature,
|
||||
};
|
||||
pub use mize_macros::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use crate::platform::os;
|
||||
|
||||
/// Global Mize instance storage.
|
||||
/// This is set once when the Mize instance is created and can be accessed from anywhere.
|
||||
pub static GLOBAL_MIZE: OnceLock<Arc<Mutex<Mize>>> = OnceLock::new();
|
||||
|
||||
/// Get the global Mize instance.
|
||||
/// Returns a clone of the Mize instance (which is cheap as it only clones Arcs internally).
|
||||
pub fn get_global_mize() -> MizeResult<Mize> {
|
||||
let arc = GLOBAL_MIZE
|
||||
.get()
|
||||
.ok_or_else(|| mize_err!("No global Mize instance set"))?;
|
||||
let mize = arc
|
||||
.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock global Mize"))?;
|
||||
Ok(mize.clone())
|
||||
}
|
||||
|
||||
/// Set the global Mize instance.
|
||||
pub fn set_global_mize(mize: Mize) -> MizeResult<()> {
|
||||
let arc = Arc::new(Mutex::new(mize.clone()));
|
||||
if let Err(_) = GLOBAL_MIZE.set(arc) {
|
||||
let a = GLOBAL_MIZE.get().unwrap();
|
||||
let mut b = a.lock().map_err(|_| mize_err!("Failed to lock global Mize"))?;
|
||||
*b = mize;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// C API function to set the global Mize instance from a dynamically loaded part.
|
||||
/// This is a hack to allow Rust parts loaded as .so files to access the global Mize instance.
|
||||
// i hate rust.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn mize_rust_hack_set_global(mize: Arc<Mutex<Mize>>, dispatch_ptr: *const std::ffi::c_void) {
|
||||
// and also set the tokio runtime
|
||||
//let mize_locked = mize.lock().unwrap();
|
||||
//let rt = mize_locked.runtime.lock().unwrap();
|
||||
//rt.enter();
|
||||
//drop(rt);
|
||||
//drop(mize_locked);
|
||||
let rt = Builder::new_multi_thread().enable_all().build().unwrap();
|
||||
let guard = rt.enter();
|
||||
std::mem::forget(guard);
|
||||
|
||||
use crate::tracing::Dispatch;
|
||||
let _ = GLOBAL_MIZE.set(mize);
|
||||
|
||||
//and also init tracing
|
||||
unsafe {
|
||||
/// i hate rust.
|
||||
let dispatch = &*(dispatch_ptr as *const Dispatch);
|
||||
if let Err(e) = tracing::dispatcher::set_global_default(dispatch.clone()) {
|
||||
println!("failed to set tracing dispatcher: {e}");
|
||||
}
|
||||
tracing::callsite::rebuild_interest_cache();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Resolve an executable name to its full path in the nixpkgs store.
|
||||
///
|
||||
/// Reads `nix.pkgs.ref` (nixpkgs commit hash) and `nix.bin` (path to the `nix`
|
||||
/// binary) from the Mize config, then runs:
|
||||
///
|
||||
/// ```text
|
||||
/// <nix.bin> eval --raw nixpkgs/<hash>#<pkg>
|
||||
/// ```
|
||||
///
|
||||
/// and returns `<outPath>/bin/<executable>`.
|
||||
///
|
||||
/// Certain executable names are mapped to different nixpkgs attribute names
|
||||
/// via a hardcoded match (e.g. `npm` → `nodejs`).
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the global Mize instance is not set, the config values are missing,
|
||||
/// or `nix eval` fails.
|
||||
pub fn msh_get_nix_bin(name: &str) -> String {
|
||||
// Hardcoded mapping of executable names to nixpkgs attribute names
|
||||
let pkg = match name {
|
||||
"npm" => "nodejs",
|
||||
"node" => "nodejs",
|
||||
"yarn" => "yarn",
|
||||
"pnpm" => "nodePackages.pnpm",
|
||||
_ => name,
|
||||
};
|
||||
|
||||
let mut mize = get_global_mize().expect("msh_get_nix_bin: no global Mize instance set");
|
||||
|
||||
let nixpkgs_ref = mize
|
||||
.get_config("nix.pkgs.ref")
|
||||
.expect("msh_get_nix_bin: failed to get config nix.pkgs.ref")
|
||||
.value_string()
|
||||
.expect("msh_get_nix_bin: nix.pkgs.ref is not a string");
|
||||
|
||||
let nix_bin = mize
|
||||
.get_config("nix.bin")
|
||||
.expect("msh_get_nix_bin: failed to get config nix.bin")
|
||||
.value_string()
|
||||
.expect("msh_get_nix_bin: nix.bin is not a string");
|
||||
|
||||
// Run: nix eval --raw nixpkgs/<hash>#<pkg>
|
||||
// --raw outputs just the store path without quotes
|
||||
let attr = format!("nixpkgs/{}#{}", nixpkgs_ref, pkg);
|
||||
let output = std::process::Command::new(&nix_bin)
|
||||
.args(["eval", "--raw", &attr])
|
||||
.output()
|
||||
.expect("msh_get_nix_bin: failed to spawn nix eval");
|
||||
|
||||
if !output.status.success() {
|
||||
panic!(
|
||||
"msh_get_nix_bin: nix eval failed for {}: {}",
|
||||
attr,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
let out_path = String::from_utf8(output.stdout)
|
||||
.expect("msh_get_nix_bin: nix eval output is not valid UTF-8")
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
format!("{}/bin/{}", out_path, name)
|
||||
}
|
||||
|
||||
/// Run a command with Mize Shell executable resolution.
|
||||
///
|
||||
/// This is the function-based equivalent of the [`msh!`] macro, with proper
|
||||
/// argument handling (no `stringify!` limitations).
|
||||
///
|
||||
/// If `cmd` contains `/` it is treated as a path and used as-is.
|
||||
/// Otherwise it is resolved via [`msh_get_nix_bin`].
|
||||
///
|
||||
/// Returns `Ok(())` if the command exits successfully, or an `io::Error` otherwise.
|
||||
pub fn msh_run(cmd: &str, args: &[&str]) -> std::io::Result<()> {
|
||||
let resolved = if cmd.contains('/') {
|
||||
cmd.to_string()
|
||||
} else {
|
||||
msh_get_nix_bin(cmd)
|
||||
};
|
||||
let mut command = std::process::Command::new(resolved);
|
||||
command.args(args);
|
||||
let status = command.status()?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("msh: command exited with {}", status),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// platform specific stuff
|
||||
pub mod platform {
|
||||
use crate::{Mize, MizeResult};
|
||||
|
||||
#[cfg(feature = "target-js")]
|
||||
pub mod js;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod os;
|
||||
|
||||
pub fn init(mize: &mut Mize) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
os::os_instance_init(mize)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-js")] {
|
||||
js::wasm_instance_init(mize)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_part(part_name: &str, path: Option<String>, at_id: Option<&str>) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
os::load_part(part_name, path, at_id)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult<String> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
os::fetch_part(mize, part_name)?;
|
||||
}
|
||||
Ok("alsdkjf".to_owned())
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use std::collections::binary_heap::Iter;
|
||||
use std::collections::HashMap;
|
||||
use std::iter::Map;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::{instrument, trace};
|
||||
use crate::tracing::instrument;
|
||||
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult};
|
||||
use crate::id::MizeId;
|
||||
@@ -13,6 +13,7 @@ use crate::instance::Mize;
|
||||
use crate::item::get_raw_from_cbor;
|
||||
use crate::item::{Item, ItemData};
|
||||
use crate::mize_err;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemStore {
|
||||
@@ -145,7 +146,7 @@ fn id_to_u64(id: MizeId) -> MizeResult<u64> {
|
||||
}
|
||||
|
||||
fn str_to_u64(id_str: &str) -> MizeResult<u64> {
|
||||
id_str.parse().mize_result_msg(format!(
|
||||
id_str.parse().mres_msg(format!(
|
||||
"Could not parse the store_part of mizeid {} into a u64 for the MemStore",
|
||||
id_str
|
||||
))
|
||||
@@ -11,10 +11,9 @@ use web_sys::{WorkerOptions, WorkerType};
|
||||
|
||||
use crate::core::item::IntoItemData;
|
||||
use crate::error::MizeResult;
|
||||
use crate::instance::module::EmptyModule;
|
||||
use crate::Mize;
|
||||
use crate::MizeError;
|
||||
use crate::{mize_err, Module};
|
||||
use crate::mize_err;
|
||||
|
||||
// console_log macro
|
||||
// that can be copied into other files for debugging purposes
|
||||
@@ -49,17 +48,72 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
pub fn wasm_instance_init(mize: &mut Mize) -> MizeResult<()> {
|
||||
console_log!("Hello world from wasm_instance_init!!!!!!!!!!");
|
||||
|
||||
#[cfg(feature = "target-obsidian")] {
|
||||
////// populate values from $MIZE_CONFIG_FILES
|
||||
if let Ok(config_file_paths) = obsidian_get_env_var("MIZE_CONFIG_FILES") {
|
||||
for config_file_path in config_file_paths.split(":") {
|
||||
println!("reading config file: {config_file_path}");
|
||||
let content = std::fs::read_to_string(config_file_path)?;
|
||||
let mut data = ItemData::from_toml(content.as_str())?;
|
||||
crate::core::item::set_config_from_data(mize, data)?;
|
||||
}
|
||||
}
|
||||
|
||||
////// load config from MIZE_CONFIG env var
|
||||
match std::env::var("MIZE_CONFIG") {
|
||||
Ok(config_string) => {
|
||||
let data = data_from_string(config_string)?;
|
||||
crate::core::item::set_config_from_data(mize, data)?;
|
||||
}
|
||||
Err(var_err) => match var_err {
|
||||
VarError::NotPresent => {
|
||||
debug!("env var MIZE_CONFIG NOT present")
|
||||
}
|
||||
VarError::NotUnicode(_) => {
|
||||
warn!("env var MIZE_CONFIG is not Unicode, so not reading it")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
pub fn obsidian_get_env_var(name: &str) -> Option<String> {
|
||||
use js_sys::{Reflect, Object};
|
||||
// 1. Get the global `process` object
|
||||
let global = js_sys::global();
|
||||
let process = Reflect::get(&global, &JsValue::from_str("process")).ok()?;
|
||||
|
||||
if process.is_undefined() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 2. Get the `env` object from `process`
|
||||
let env = Reflect::get(&process, &JsValue::from_str("env")).ok()?;
|
||||
if env.is_undefined() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 3. Get the `MIZE_STORE` value from `env`
|
||||
let mize_store = Reflect::get(&env, &JsValue::from_str("MIZE_STORE")).ok()?;
|
||||
|
||||
// 4. Convert the JsValue to a Rust String if it exists
|
||||
mize_store.as_string()
|
||||
}
|
||||
|
||||
|
||||
pub fn load_module(mize: &mut Mize, name: &str, path: Option<String>) -> MizeResult<()> {
|
||||
console_log!("loading module: {}", name);
|
||||
console_log!("loading module NOT IMPLEMENTED: {}", name);
|
||||
|
||||
wasm_bindgen_futures::spawn_local(load_module_async(mize.clone(), name.to_owned(), path));
|
||||
//wasm_bindgen_futures::spawn_local(load_module_async(mize.clone(), name.to_owned(), path));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
async fn load_module_async(mut mize: Mize, name: String, path: Option<String>) -> () {
|
||||
let name = name.as_str();
|
||||
let module_url = fetch_module(&mut mize, name).unwrap();
|
||||
@@ -145,8 +199,9 @@ async fn load_module_async(mut mize: Mize, name: String, path: Option<String>) -
|
||||
}
|
||||
*/
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn fetch_module(mize: &mut Mize, name: &str) -> MizeResult<String> {
|
||||
pub fn fetch_module(mize: &mut Mize, name: &str) -> MizeResult<String> {
|
||||
let module_name_with_slashes = name.replace(".", "/");
|
||||
if let Ok(module_path) = mize
|
||||
.get(format!(
|
||||
@@ -191,6 +246,7 @@ async fn download_module(mize: &mut Mize, name: &str) -> MizeResult<js_sys::Arra
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct JsMize {
|
||||
inner: NonNull<Mize>,
|
||||
}
|
||||
@@ -207,10 +263,7 @@ pub async fn new_js_instance(config_json_str: String) -> JsMize {
|
||||
|
||||
let config = ItemData::from_json(config_json_str).expect("parsing of json config failed");
|
||||
|
||||
let mut mize = Mize::empty().expect("Mize::empty() failed");
|
||||
|
||||
mize.set_blocking("0/config", config)
|
||||
.expect("Failed to set the config at item 0");
|
||||
let mut mize = Mize::with_config(config).expect("Mize::new() failed");
|
||||
|
||||
let mut js_instance = JsMize {
|
||||
inner: NonNull::from(Box::leak(Box::new(mize))),
|
||||
@@ -4,17 +4,19 @@ use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use sysinfo::{Pid, ProcessRefreshKind, RefreshKind, System};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::core::id::MizeId;
|
||||
use crate::core::item::{Item, ItemData};
|
||||
use crate::id::MizeId;
|
||||
use crate::item::{Item, ItemData};
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult};
|
||||
use crate::instance::store::IdIter;
|
||||
use crate::instance::{self, Mize};
|
||||
use crate::item::get_raw_from_cbor;
|
||||
use crate::item::IntoItemData;
|
||||
use crate::memstore::MemStore;
|
||||
use crate::{core::instance::store::Store, mize_err};
|
||||
use crate::{instance::store::Store, mize_err};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -72,6 +74,7 @@ impl FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn try_connect_to_sock(store_path: String) -> MizeResult<()> {
|
||||
match UnixStream::connect(PathBuf::from(store_path).join("sock")).await {
|
||||
Err(_) => Err(mize_err!("")),
|
||||
@@ -79,15 +82,30 @@ async fn try_connect_to_sock(store_path: String) -> MizeResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
async fn try_connect_to_sock(store_path: String) -> MizeResult<()> {
|
||||
use tokio::net::windows::named_pipe::{ClientOptions};
|
||||
let pipe_name = format!(
|
||||
"\\.\\pipe\\mize_{}",
|
||||
store_path.replace('\\', "_").replace('/', "_").replace(':', "_")
|
||||
);
|
||||
// ClientOptions::new().open() is synchronous, not async
|
||||
// So we just try to open and immediately close it
|
||||
match ClientOptions::new().open(&pipe_name) {
|
||||
Err(_) => Err(mize_err!("")),
|
||||
Ok(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
impl Store for FileStore {
|
||||
fn new_id(&self) -> MizeResult<String> {
|
||||
let mut next_id: u64 =
|
||||
String::from_utf8(fs::read(self.path.join("next_id")).mize_result_msg(format!(
|
||||
String::from_utf8(fs::read(self.path.join("next_id")).mres_msg(format!(
|
||||
"could not read next_id at '{}'",
|
||||
self.path.display()
|
||||
))?)?
|
||||
.parse()
|
||||
.mize_result_msg(format!(
|
||||
.mres_msg(format!(
|
||||
"could not parse next_id at '{}' to u64",
|
||||
self.path.display()
|
||||
))?;
|
||||
@@ -132,7 +150,7 @@ impl Store for FileStore {
|
||||
|
||||
let file = OpenOptions::new().read(true).create(true).open(path)?;
|
||||
|
||||
let cbor_val: CborValue = ciborium::from_reader(file).mize_result_msg(format!(
|
||||
let cbor_val: CborValue = ciborium::from_reader(file).mres_msg(format!(
|
||||
"could not read file '{}' from FileStore",
|
||||
self.path.join("store").join(id.store_part()).display()
|
||||
))?;
|
||||
@@ -163,17 +181,20 @@ impl Store for FileStore {
|
||||
)
|
||||
})?;
|
||||
|
||||
let cbor_value: CborValue = ciborium::from_reader(file).mize_result_msg(format!(
|
||||
let cbor_value: CborValue = ciborium::from_reader(file).mres_msg(format!(
|
||||
"could not read file '{}' from FileStore",
|
||||
self.path.join("store").join(id.store_part()).display()
|
||||
))?;
|
||||
let data: ItemData = cbor_value.into_item_data();
|
||||
|
||||
let tmp = id.path();
|
||||
let mut path_iter = tmp.into_iter();
|
||||
let mut path_iter = tmp.iter();
|
||||
path_iter.next();
|
||||
let new_path: Vec<String> = path_iter.map(|v| v.to_owned()).collect();
|
||||
//println!("data: {}", data);
|
||||
//println!("new_path: {:?}", new_path);
|
||||
let ret_data = data.get_path(new_path)?;
|
||||
//println!("ret_data: {}", ret_data);
|
||||
|
||||
return Ok(ret_data);
|
||||
}
|
||||
@@ -188,6 +209,7 @@ impl Store for FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
fn valid_pid_file(path: &Path) -> MizeResult<Option<u32>> {
|
||||
let pid_file_path = path.join("pid");
|
||||
|
||||
@@ -197,12 +219,12 @@ fn valid_pid_file(path: &Path) -> MizeResult<Option<u32>> {
|
||||
}
|
||||
|
||||
// read pid file
|
||||
let pid: u32 = String::from_utf8(fs::read(&pid_file_path).mize_result_msg(format!(
|
||||
let pid: u32 = String::from_utf8(fs::read(&pid_file_path).mres_msg(format!(
|
||||
"Could not read contents of pid file at '{}'",
|
||||
pid_file_path.display()
|
||||
))?)?
|
||||
.parse()
|
||||
.mize_result_msg(format!(
|
||||
.mres_msg(format!(
|
||||
"Could not parse contents of pid file at '{}' to u32",
|
||||
pid_file_path.display()
|
||||
))?;
|
||||
414
core/src/platform/os/logging.rs
Normal file
414
core/src/platform/os/logging.rs
Normal file
@@ -0,0 +1,414 @@
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use clap::{ArgAction, ArgMatches};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use std::env;
|
||||
use std::fmt::Write;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing::{Dispatch, Event};
|
||||
use tracing::{field::Visit, Id, Subscriber};
|
||||
use tracing::{span_enabled, Instrument, Level};
|
||||
use tracing_core::Field;
|
||||
use tracing_subscriber::filter::EnvFilter;
|
||||
use tracing_subscriber::fmt::format;
|
||||
use tracing_subscriber::fmt::FmtContext;
|
||||
use tracing_subscriber::fmt::FormatEvent;
|
||||
use tracing_subscriber::fmt::FormatFields;
|
||||
use tracing_subscriber::fmt::FormattedFields;
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
|
||||
use crate::id::MizeId;
|
||||
use crate::item::ItemData;
|
||||
use crate::MizeError;
|
||||
use crate::MizeResult;
|
||||
use crate::prelude::*;
|
||||
|
||||
static DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::WARN;
|
||||
|
||||
|
||||
/*
|
||||
#[macro_export]
|
||||
macro_rules! trace {
|
||||
($($arg:tt)*) => {
|
||||
// format_args! handles string interpolation safely at compile time
|
||||
if let Err(e) = $crate::write_to_mize_stdout(format_args!("[ {} ] {}\n", colored::Colorize::yellow("TRACE"), format_args!($($arg)*))) {
|
||||
// Choose your error handling behavior here (panic, print to stderr, or ignore)
|
||||
eprintln!("out! macro failed to write!!");
|
||||
}
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)*) => {
|
||||
// format_args! handles string interpolation safely at compile time
|
||||
if let Err(e) = $crate::write_to_mize_stdout(format_args!("[ {} ] {}\n", colored::Colorize::green("DEBUG"), format_args!($($arg)*))) {
|
||||
// Choose your error handling behavior here (panic, print to stderr, or ignore)
|
||||
eprintln!("out! macro failed to write!!");
|
||||
}
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($arg:tt)*) => {
|
||||
// format_args! handles string interpolation safely at compile time
|
||||
if let Err(e) = $crate::write_to_mize_stdout(format_args!("[ {} ] {}\n", colored::Colorize::blue("INFO"), format_args!($($arg)*))) {
|
||||
// Choose your error handling behavior here (panic, print to stderr, or ignore)
|
||||
eprintln!("out! macro failed to write!!");
|
||||
}
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => {
|
||||
// format_args! handles string interpolation safely at compile time
|
||||
if let Err(e) = $crate::write_to_mize_stdout(format_args!("[ {} ] {}\n", colored::Colorize::truecolor("WARN", 245, 164, 66), format_args!($($arg)*))) {
|
||||
// Choose your error handling behavior here (panic, print to stderr, or ignore)
|
||||
eprintln!("out! macro failed to write!!");
|
||||
}
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)*) => {
|
||||
// format_args! handles string interpolation safely at compile time
|
||||
if let Err(e) = $crate::write_to_mize_stdout(format_args!("[ {} ] {}\n", colored::Colorize::red("ERROR"), format_args!($($arg)*))) {
|
||||
// Choose your error handling behavior here (panic, print to stderr, or ignore)
|
||||
eprintln!("out! macro failed to write!!");
|
||||
}
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn init_logger(cli_matches: Option<&ArgMatches>) {
|
||||
trace!("init_logger called");
|
||||
// to save log messages, before the logger is setup
|
||||
let mut log_messages: Vec<(Level, String)> = Vec::new();
|
||||
|
||||
log_messages.push((Level::INFO, "Starting Mize".to_owned()));
|
||||
|
||||
// builder with DEFAULT_LOG_LEVEL
|
||||
let mut builder = tracing_subscriber::fmt::Subscriber::builder();
|
||||
|
||||
// set loglevel
|
||||
let mut log_level = DEFAULT_LOG_LEVEL;
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
format!(
|
||||
"set log level from DEFAULT_LOG_LEVEL to {}",
|
||||
DEFAULT_LOG_LEVEL
|
||||
),
|
||||
));
|
||||
|
||||
// check env RUST_LOG
|
||||
let mut val_rust_log = level_from_env("RUST_LOG", log_level);
|
||||
if let Some(level_rust_log) = val_rust_log.0 {
|
||||
log_level = level_rust_log;
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
format!(
|
||||
"set log level from the Variable RUST_LOG to {}",
|
||||
level_rust_log
|
||||
),
|
||||
));
|
||||
}
|
||||
log_messages.append(&mut val_rust_log.1);
|
||||
|
||||
// check env LOG
|
||||
let mut val_mize_log = level_from_env("LOG", log_level);
|
||||
if let Some(level_mize_log) = val_mize_log.0 {
|
||||
debug!("set log level from the Variable LOG to {}", level_mize_log);
|
||||
log_level = level_mize_log;
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
format!(
|
||||
"set log level from the Variable MIZE_LOG to {}",
|
||||
level_mize_log
|
||||
),
|
||||
));
|
||||
}
|
||||
log_messages.append(&mut val_mize_log.1);
|
||||
|
||||
// check env MIZE_LOG
|
||||
let mut val_mize_log = level_from_env("MIZE_LOG", log_level);
|
||||
if let Some(level_mize_log) = val_mize_log.0 {
|
||||
log_level = level_mize_log;
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
format!(
|
||||
"set log level from the Variable MIZE_LOG to {}",
|
||||
level_mize_log
|
||||
),
|
||||
));
|
||||
}
|
||||
log_messages.append(&mut val_mize_log.1);
|
||||
|
||||
// check -vvvvv flags
|
||||
if let Some(cli_matches) = cli_matches {
|
||||
let v_flag_count = cli_matches.get_count("verbose");
|
||||
if v_flag_count > 0 {
|
||||
let default_log_level_as_num = match DEFAULT_LOG_LEVEL {
|
||||
LevelFilter::OFF => 0,
|
||||
LevelFilter::ERROR => 1,
|
||||
LevelFilter::WARN => 2,
|
||||
LevelFilter::INFO => 3,
|
||||
LevelFilter::DEBUG => 4,
|
||||
LevelFilter::TRACE => 5,
|
||||
};
|
||||
let new_log_level_num = default_log_level_as_num + v_flag_count;
|
||||
log_level = match new_log_level_num {
|
||||
0 => LevelFilter::OFF,
|
||||
1 => LevelFilter::ERROR,
|
||||
2 => LevelFilter::WARN,
|
||||
3 => LevelFilter::INFO,
|
||||
4 => LevelFilter::DEBUG,
|
||||
5 => LevelFilter::TRACE,
|
||||
_ => {
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
format!(
|
||||
"the number of verbose (-v) flags passed ({}) exeeds the maximumg log_level",
|
||||
v_flag_count
|
||||
),
|
||||
));
|
||||
LevelFilter::TRACE
|
||||
}
|
||||
};
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
format!("set log level from the verbose flags to {}", log_level),
|
||||
));
|
||||
}
|
||||
|
||||
// check --log-level arg
|
||||
if let Some(log_level_arg_string) = cli_matches.get_one::<String>("log-level") {
|
||||
let log_level_from_arg = match log_level_arg_string.to_owned().to_lowercase().as_str() {
|
||||
"none" => LevelFilter::OFF,
|
||||
"trace" => LevelFilter::TRACE,
|
||||
"debug" => LevelFilter::DEBUG,
|
||||
"info" => LevelFilter::INFO,
|
||||
"warn" => LevelFilter::WARN,
|
||||
"error" => LevelFilter::ERROR,
|
||||
_ => {
|
||||
log_messages.push((Level::WARN, format!(
|
||||
"The argument to --log-level \"{}\" is not a valid log-level. loglevel stays at: {}"
|
||||
, log_level_arg_string
|
||||
, log_level
|
||||
)));
|
||||
log_level
|
||||
}
|
||||
};
|
||||
log_level = log_level_from_arg;
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
format!(
|
||||
"set log level from the --log-level arg to {}",
|
||||
log_level_from_arg
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// actually set the log_level on the builder
|
||||
log_messages.push((Level::INFO, format!("loglevel is now {}", log_level)));
|
||||
|
||||
// filter loging
|
||||
//let mut my_env_filter = tracing_subscriber::filter::EnvFilter::new("")
|
||||
//.add_directive("[get_raw_from_cbor]=debug".parse().expect("error parsing log directive"))
|
||||
//.add_directive("[MemStore::get_value_raw]=trace".parse().expect("error parsing log directive"))
|
||||
//.add_directive("trace".parse().unwrap())
|
||||
//.add_directive("[get_raw_from_cbor]=debug".parse().expect("error parsing log directive"))
|
||||
//;
|
||||
|
||||
//MinimalTracer::register();
|
||||
|
||||
debug!("log_level: {}", log_level);
|
||||
let field_formatter = tracing_subscriber::fmt::format::debug_fn(|writer, field, value| {
|
||||
if field.name() == "message" {
|
||||
write!(writer, "{:?}", value)?;
|
||||
} else {
|
||||
write!(writer, "{}={:?}", field, value)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
let subscriber = builder
|
||||
//.with_env_filter(my_env_filter)
|
||||
.without_time()
|
||||
.event_format(MyFormatter)
|
||||
.fmt_fields(field_formatter)
|
||||
.with_max_level(log_level)
|
||||
.finish();
|
||||
|
||||
let dispatch = Dispatch::new(subscriber);
|
||||
if let Err(e) = tracing::dispatcher::set_global_default(dispatch) {
|
||||
println!("faild to set_global_default(dispatch): {e}");
|
||||
}
|
||||
|
||||
// write out all log messages from log_messages
|
||||
for (level, msg) in log_messages {
|
||||
match level {
|
||||
Level::ERROR => error!("{}", msg),
|
||||
Level::WARN => warn!("{}", msg),
|
||||
Level::INFO => info!("{}", msg),
|
||||
Level::DEBUG => debug!("{}", msg),
|
||||
Level::TRACE => trace!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn level_from_env(
|
||||
var_name: &str,
|
||||
log_level: LevelFilter,
|
||||
) -> (Option<LevelFilter>, Vec<(Level, String)>) {
|
||||
let mut log_messages: Vec<(Level, String)> = Vec::new();
|
||||
let var_value = env::var(var_name);
|
||||
debug!("checking env var: {}={:?}", var_name, var_value);
|
||||
|
||||
match var_value {
|
||||
Ok(val) => {
|
||||
// set loglevel acording to RUST_LOG
|
||||
let level = match val.to_lowercase().as_str() {
|
||||
"none" => LevelFilter::OFF,
|
||||
"trace" => LevelFilter::TRACE,
|
||||
"debug" => LevelFilter::DEBUG,
|
||||
"info" => LevelFilter::INFO,
|
||||
"warn" => LevelFilter::WARN,
|
||||
"error" => LevelFilter::ERROR,
|
||||
_ => {
|
||||
log_messages.push((Level::WARN, format!(
|
||||
"The value of Environment Variable {}: \"{}\" is not a valid log-level. loglevel stays at: {}"
|
||||
, var_name
|
||||
, val
|
||||
, log_level
|
||||
)));
|
||||
log_level
|
||||
}
|
||||
};
|
||||
return (Some(level), log_messages);
|
||||
}
|
||||
Err(env::VarError::NotPresent) => {}
|
||||
Err(env::VarError::NotUnicode(os_string)) => {
|
||||
let os_string_as_hex: String = os_string
|
||||
.clone()
|
||||
.into_encoded_bytes()
|
||||
.iter()
|
||||
.map(|b| format!("{:02x}", b).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
log_messages.push((Level::DEBUG, format!(
|
||||
"value of Environment Variable {} was not a unicode valid string. Therefore ignoring it.
|
||||
\n value with replacement char U+FFFD: {}
|
||||
\n the WTF-8 raw bytes as hex: {}"
|
||||
, var_name
|
||||
, os_string.to_string_lossy(), os_string_as_hex
|
||||
)));
|
||||
}
|
||||
};
|
||||
return (None, log_messages);
|
||||
}
|
||||
|
||||
struct MyFormatter;
|
||||
|
||||
impl<S, N> FormatEvent<S, N> for MyFormatter
|
||||
where
|
||||
S: tracing::Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'a> FormatFields<'a> + 'static,
|
||||
{
|
||||
fn format_event(
|
||||
&self,
|
||||
ctx: &FmtContext<'_, S, N>,
|
||||
mut writer: format::Writer<'_>,
|
||||
event: &Event<'_>,
|
||||
) -> fmt::Result {
|
||||
// Format values from the event's's metadata:
|
||||
|
||||
let metadata = event.metadata();
|
||||
|
||||
// set format
|
||||
//builder.format(|buf, record| match record.level() {});
|
||||
|
||||
let level_str = match metadata.level() {
|
||||
&Level::TRACE => format!("[ {} ]", "TRACE".yellow()),
|
||||
&Level::DEBUG => format!("[ {} ]", "DEBUG".green()),
|
||||
&Level::INFO => format!("[ {} ]", "INFO".blue()),
|
||||
&Level::WARN => format!("[ {} ]", "WARN".truecolor(245, 164, 66)),
|
||||
&Level::ERROR => format!("[ {} ]", "ERROR".red()),
|
||||
};
|
||||
|
||||
// with target
|
||||
// write!(&mut writer, "{} {}: ", level_str, metadata.target())?;
|
||||
|
||||
write!(&mut writer, "{} ", level_str)?;
|
||||
|
||||
// Format all the spans in the event's span context.
|
||||
if let Some(scope) = ctx.event_scope() {
|
||||
for span in scope.from_root() {
|
||||
write!(writer, "{}", span.name())?;
|
||||
|
||||
// `FormattedFields` is a formatted representation of the span's
|
||||
// fields, which is stored in its extensions by the `fmt` layer's
|
||||
// `new_span` method. The fields will have been formatted
|
||||
// by the same field formatter that's provided to the event
|
||||
// formatter in the `FmtContext`.
|
||||
let ext = span.extensions();
|
||||
let fields = &ext
|
||||
.get::<FormattedFields<N>>()
|
||||
.expect("will never be `None`");
|
||||
|
||||
// Skip formatting the fields if the span had no fields.
|
||||
if !fields.is_empty() {
|
||||
//write!(writer, "{{{}}}", fields)?;
|
||||
}
|
||||
write!(writer, ": ")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Write fields on the event
|
||||
ctx.field_format().format_fields(writer.by_ref(), event)?;
|
||||
|
||||
writeln!(writer);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// thanks: https://stackoverflow.com/a/75546824
|
||||
pub struct StringVisitor<'a> {
|
||||
string: &'a mut String,
|
||||
}
|
||||
impl<'a> StringVisitor<'a> {
|
||||
pub(crate) fn new(string: &'a mut String) -> Self {
|
||||
StringVisitor { string }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visit for StringVisitor<'a> {
|
||||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
||||
if field.name() == "message" {
|
||||
write!(self.string, "{value:?} ").unwrap();
|
||||
} else {
|
||||
write!(self.string, "{} = {:?}; ", field.name(), value).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LogFilter {
|
||||
target: Option<String>,
|
||||
level: Option<Level>,
|
||||
}
|
||||
|
||||
fn string_to_level(string: &str) -> Option<Level> {
|
||||
match string.to_lowercase().as_str() {
|
||||
"info" => Some(Level::INFO),
|
||||
"debug" => Some(Level::DEBUG),
|
||||
"warn" | "warning" => Some(Level::WARN),
|
||||
"trace" => Some(Level::TRACE),
|
||||
"error" => Some(Level::ERROR),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
653
core/src/platform/os/mod.rs
Normal file
653
core/src/platform/os/mod.rs
Normal file
@@ -0,0 +1,653 @@
|
||||
use clap::ArgMatches;
|
||||
use colored::Colorize;
|
||||
use tracing::Dispatch;
|
||||
use core::fmt;
|
||||
use flate2::bufread::GzDecoder;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::env::VarError;
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tar::Archive;
|
||||
use tracing::Event;
|
||||
use tracing::field::Visit;
|
||||
use tracing::Level;
|
||||
use tracing_core::Field;
|
||||
use tracing_subscriber::fmt::format;
|
||||
use tracing_subscriber::fmt::FmtContext;
|
||||
use tracing_subscriber::fmt::FormatEvent;
|
||||
use tracing_subscriber::fmt::FormatFields;
|
||||
use tracing_subscriber::fmt::FormattedFields;
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::item::data_from_string;
|
||||
|
||||
use self::fsstore::FileStore;
|
||||
|
||||
pub mod fsstore;
|
||||
pub mod logging;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
mod unix_socket;
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
mod windows_socket;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::platform::os::unix_socket::UnixListener;
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
use crate::platform::os::windows_socket::WindowsListener as UnixListener;
|
||||
//mod web;
|
||||
|
||||
pub fn os_instance_init(mize: &mut Mize) -> MizeResult<()> {
|
||||
// this is the code, that runs to initialize an Instance on a system with an os present.
|
||||
|
||||
|
||||
// setup loging to stderr if enabled
|
||||
if mize
|
||||
.get_config("log.stderr.enable")
|
||||
.unwrap_or(ItemData::new())
|
||||
.value_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
use tracing::Level;
|
||||
let level_string = mize
|
||||
.get_config("log.level")?
|
||||
.value_string()
|
||||
.unwrap_or("info".to_string());
|
||||
let log_level = match level_string.to_lowercase().as_str() {
|
||||
"info" => Level::INFO,
|
||||
"debug" => Level::DEBUG,
|
||||
"warn" | "warning" => Level::WARN,
|
||||
"trace" => Level::TRACE,
|
||||
"error" => Level::ERROR,
|
||||
_ => Level::INFO, // the default log level
|
||||
};
|
||||
println!("enabling loggging to stderr with log_level: {}", log_level);
|
||||
|
||||
// builder
|
||||
let builder = tracing_subscriber::fmt::Subscriber::builder();
|
||||
|
||||
let field_formatter = tracing_subscriber::fmt::format::debug_fn(|writer, field, value| {
|
||||
if field.name() == "message" {
|
||||
write!(writer, "{:?}", value)?;
|
||||
} else {
|
||||
//write!(writer, "{}={:?}", field, value)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
builder
|
||||
//.with_env_filter(my_env_filter)
|
||||
.without_time()
|
||||
.event_format(MyFormatter)
|
||||
.fmt_fields(field_formatter)
|
||||
.with_max_level(log_level)
|
||||
.init();
|
||||
}
|
||||
|
||||
////// if a config.store_path is set, upgrade to the filestore there
|
||||
let store_path = match mize.get_config("store")?.value_string() {
|
||||
Ok(path) => path,
|
||||
Err(_e) => {
|
||||
// the default store_path: $HOME/.mize
|
||||
let home_dir = if let Some(dir) = std::env::home_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(mize_err!(
|
||||
"could not get home_dir and store_path is not set in the config"
|
||||
));
|
||||
};
|
||||
|
||||
let store_path_buff = home_dir.join(".mize");
|
||||
let store_path = store_path_buff.display().to_string();
|
||||
|
||||
mize.set_blocking(
|
||||
"self/config/store_path",
|
||||
store_path.clone().into_item_data(),
|
||||
)?;
|
||||
|
||||
let store_path = mize.get("self/config/store_path")?.value_string()?;
|
||||
|
||||
println!("store_path: {}", store_path);
|
||||
|
||||
store_path
|
||||
}
|
||||
};
|
||||
|
||||
if FileStore::store_is_opened(store_path.to_owned(), mize)? {
|
||||
// if the store is already opened, connect to the instance, that opened it and join
|
||||
// it's namespace
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
info!("CONNECTING");
|
||||
use crate::platform::os::unix_socket;
|
||||
unix_socket::connect(mize, store_path.into())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
info!("CONNECTING");
|
||||
use crate::platform::os::windows_socket;
|
||||
windows_socket::connect(mize, store_path.into())?;
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
// else open it ourselves
|
||||
let file_store = FileStore::new(store_path.as_str())?;
|
||||
mize.migrate_to_store(Box::new(file_store))?;
|
||||
|
||||
let path = Path::new(&store_path).to_owned();
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
mize.add_conn_listener(UnixListener::new(path)?)?;
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
mize.add_conn_listener(UnixListener::new(path)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn seconds_since_modification(path: &Path) -> MizeResult<u64> {
|
||||
let metadata = fs::metadata(path)?;
|
||||
|
||||
let modified = metadata.modified()?;
|
||||
|
||||
let duration = modified.duration_since(UNIX_EPOCH)?;
|
||||
|
||||
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(
|
||||
mize: &mut Mize,
|
||||
name: &str,
|
||||
options: ItemData,
|
||||
) -> MizeResult<(String, String)> {
|
||||
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...
|
||||
debug!("get_part_hash: name: {}", name);
|
||||
let mut name_parts = name.split(".");
|
||||
if name_parts
|
||||
.next()
|
||||
.ok_or(mize_err!("no 0th element in the modName"))?
|
||||
== "cross"
|
||||
{
|
||||
let system = name_parts
|
||||
.next()
|
||||
.ok_or(mize_err!("no 1th element in the modName"))?;
|
||||
let mod_name: String = name_parts.collect();
|
||||
println!("get_part_hash: mod_name: {}", mod_name);
|
||||
if mod_name == "" {
|
||||
return Err(mize_err!("no 2nd element in the modName"));
|
||||
}
|
||||
|
||||
selector_data.set_path("system", system)?;
|
||||
selector_data.set_path("modName", mod_name)?;
|
||||
} else {
|
||||
selector_data.set_path("modName", name)?;
|
||||
}
|
||||
|
||||
selector_data.merge(options);
|
||||
|
||||
// important to produce the same hash for the sama selector data
|
||||
selector_data.sort_keys();
|
||||
|
||||
let selector_str = selector_data.to_json()?;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.update(selector_str.clone().as_bytes());
|
||||
|
||||
let result = hasher.finalize();
|
||||
|
||||
let mut hex: String = result
|
||||
.iter()
|
||||
.map(|b| format!("{:02x}", b).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
hex.truncate(32);
|
||||
|
||||
return Ok((hex, selector_str));
|
||||
}
|
||||
|
||||
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 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 parts_path = format!("{store_path}/parts");
|
||||
let target_dir = format!("{parts_path}/{part_hash}-{part_name}");
|
||||
|
||||
let tmp_file_path_gz = format!("{parts_path}/{artifact}");
|
||||
std::fs::create_dir_all(&parts_path)?;
|
||||
|
||||
let tmp_gz_file = if !PathBuf::from(tmp_file_path_gz.clone()).exists() {
|
||||
let mut tmp_gz_file = File::create(&tmp_file_path_gz)?;
|
||||
|
||||
debug!(
|
||||
"downloading part '{}' from {} (selector: {})",
|
||||
part_name, part_url, part_selector
|
||||
);
|
||||
|
||||
let response = http_req::request::get(&part_url, &mut tmp_gz_file)?;
|
||||
|
||||
let code = response.status_code();
|
||||
if code != 200.into() {
|
||||
return Err(mize_err!(
|
||||
"failed to get part '{}' with hash '{}' and selector: {}",
|
||||
part_name,
|
||||
part_hash,
|
||||
part_selector
|
||||
)
|
||||
.msg(format!(
|
||||
"the http request to fetch the part from '{}' returned a status code of: {}",
|
||||
part_url, code
|
||||
)));
|
||||
}
|
||||
|
||||
tmp_gz_file
|
||||
} else {
|
||||
std::fs::File::open(tmp_file_path_gz.clone())?
|
||||
};
|
||||
|
||||
let tar = GzDecoder::new(std::io::BufReader::new(tmp_gz_file));
|
||||
let mut archive = Archive::new(tar);
|
||||
|
||||
//archive.set_mask(umask::Mode::parse("rwxrwxrwx")?.into());
|
||||
|
||||
std::fs::create_dir_all(target_dir.as_str())?;
|
||||
|
||||
archive.unpack(target_dir.as_str())?;
|
||||
|
||||
fs::remove_file(&tmp_file_path_gz)?;
|
||||
|
||||
Ok(format!("{parts_path}/{part_hash}-{part_name}"))
|
||||
}
|
||||
|
||||
fn get_part_path(mize: &mut Mize, part_name: &str, _path: Option<String>) -> MizeResult<String> {
|
||||
|
||||
let part_symbol_name = part_name.replace(".", "_");
|
||||
|
||||
// check if $name.src is set
|
||||
if let Ok(itm) = mize.get_config(format!("{part_name}.src").as_str()) {
|
||||
let part_src = itm.value_string().map_err(|e| e.msg(format!("opt {part_name}.src was not a string")))?;
|
||||
// will only work for rust for now
|
||||
let part_path = format!("{part_src}/target/debug/libmize_part_{part_symbol_name}.so");
|
||||
return Ok(part_path);
|
||||
}
|
||||
|
||||
// check if $name.out is set
|
||||
if let Ok(itm) = mize.get_config(format!("{part_name}.out").as_str()) {
|
||||
let part_out = itm.value_string().map_err(|e| e.msg(format!("opt {part_name}.out was not a string")))?;
|
||||
let part_path = format!("{part_out}/lib/libmize_part_{part_symbol_name}.so");
|
||||
return Ok(part_path);
|
||||
}
|
||||
|
||||
// check if we find the part inside mize.src
|
||||
if let Ok(itm) = mize.get_config("mize.src") {
|
||||
let mize_src = itm.value_string().map_err(|e| e.msg("opt mize.src was not a string"))?;
|
||||
let part_path = format!("{mize_src}/target/debug/libmize_part_{part_symbol_name}.so");
|
||||
return Ok(part_path);
|
||||
}
|
||||
|
||||
// fetch the part from https://mize.works
|
||||
let out_path = fetch_part(mize, part_name)?;
|
||||
return Ok(format!("{out_path}/lib/libmize_part_{part_symbol_name}.so"));
|
||||
|
||||
//Err(mize_err!("part {part_name} not found"))
|
||||
}
|
||||
|
||||
/// Load a part `.so` for `part_name`, registering it under `at_id` (or `part_name` if None).
|
||||
/// 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)? };
|
||||
|
||||
unsafe {
|
||||
if let Ok(rust_hack_func) = lib.get::<libloading::Symbol<unsafe extern "C" fn(Arc<Mutex<Mize>>, *const std::ffi::c_void)>>("mize_rust_hack_set_global".as_bytes()) {
|
||||
|
||||
tracing::dispatcher::get_default(|dispatch| {
|
||||
rust_hack_func(GLOBAL_MIZE.get().unwrap().clone(), dispatch as *const Dispatch as *const std::ffi::c_void);
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
let part_symbol_name = part_name.replace(".", "_");
|
||||
|
||||
let func: libloading::Symbol<unsafe extern "C" fn()> =
|
||||
unsafe { lib.get(format!("part_{}", part_symbol_name).as_bytes())? };
|
||||
|
||||
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()?;
|
||||
opts.entry(part_id.clone()).or_insert_with(|| ConfigOpt {
|
||||
name: eval_name.to_owned(),
|
||||
val: Some(ItemData::new()),
|
||||
fun: None,
|
||||
deps: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let prev_eval = mize.clone().cur_eval_part()?;
|
||||
mize.clone().set_cur_eval_part(Some(part_id))?;
|
||||
crate::c_api::clear_err();
|
||||
unsafe { func() };
|
||||
let c_err = crate::c_api::take_err();
|
||||
mize.clone().set_cur_eval_part(prev_eval)?;
|
||||
|
||||
// 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
|
||||
std::mem::forget(lib);
|
||||
|
||||
if let Some(err) = c_err {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config_from_cli_args(matches: &ArgMatches) -> MizeResult<ItemData> {
|
||||
let mut config = ItemData::new();
|
||||
|
||||
////// populate values from $MIZE_CONFIG_FILES
|
||||
if let Ok(config_file_paths) = std::env::var("MIZE_CONFIG_FILES") {
|
||||
for config_file_path in config_file_paths.split(":") {
|
||||
println!("reading config file: {config_file_path}");
|
||||
let content = std::fs::read_to_string(config_file_path)?;
|
||||
let data = ItemData::from_toml(content.as_str())?;
|
||||
config.merge(data);
|
||||
}
|
||||
}
|
||||
|
||||
////// load config from MIZE_CONFIG env var
|
||||
match std::env::var("MIZE_CONFIG") {
|
||||
Ok(config_string) => {
|
||||
let data = data_from_string(config_string)?;
|
||||
config.merge(data);
|
||||
}
|
||||
Err(var_err) => match var_err {
|
||||
VarError::NotPresent => {
|
||||
debug!("env var MIZE_CONFIG NOT present")
|
||||
}
|
||||
VarError::NotUnicode(_) => {
|
||||
warn!("env var MIZE_CONFIG is not Unicode, so not reading it")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(config_file_path) = matches.get_one::<String>("config") {
|
||||
config.merge(config_from_file(config_file_path.to_string())?);
|
||||
trace!("config after --config arg: {}", config);
|
||||
}
|
||||
|
||||
if let Some(config_string) = matches.get_one::<String>("opt") {
|
||||
config.merge(data_from_string(config_string.to_string())?);
|
||||
trace!("config after --opt arg: {}", config);
|
||||
}
|
||||
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
pub fn config_from_file(file_path: String) -> MizeResult<ItemData> {
|
||||
let toml_string = fs::read_to_string(&file_path)
|
||||
.mres_msg(format!("Could not read file {} as mize config", &file_path))?;
|
||||
|
||||
let item_data = ItemData::from_toml(toml_string.as_str())?;
|
||||
|
||||
return Ok(item_data);
|
||||
}
|
||||
|
||||
struct MyFormatter;
|
||||
|
||||
impl<S, N> FormatEvent<S, N> for MyFormatter
|
||||
where
|
||||
S: tracing::Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'a> FormatFields<'a> + 'static,
|
||||
{
|
||||
fn format_event(
|
||||
&self,
|
||||
ctx: &FmtContext<'_, S, N>,
|
||||
mut writer: format::Writer<'_>,
|
||||
event: &Event<'_>,
|
||||
) -> fmt::Result {
|
||||
// Format values from the event's's metadata:
|
||||
|
||||
let metadata = event.metadata();
|
||||
|
||||
// set format
|
||||
//builder.format(|buf, record| match record.level() {});
|
||||
|
||||
let level_str = match metadata.level() {
|
||||
&Level::TRACE => format!("[ {} ]", "TRACE".yellow()),
|
||||
&Level::DEBUG => format!("[ {} ]", "DEBUG".green()),
|
||||
&Level::INFO => format!("[ {} ]", "INFO".blue()),
|
||||
&Level::WARN => format!("[ {} ]", "WARN".truecolor(245, 164, 66)),
|
||||
&Level::ERROR => format!("[ {} ]", "ERROR".red()),
|
||||
};
|
||||
|
||||
// with target
|
||||
// write!(&mut writer, "{} {}: ", level_str, metadata.target())?;
|
||||
|
||||
write!(&mut writer, "{} ", level_str)?;
|
||||
|
||||
// Format all the spans in the event's span context.
|
||||
if let Some(scope) = ctx.event_scope() {
|
||||
for span in scope.from_root() {
|
||||
write!(writer, "{}", span.name())?;
|
||||
|
||||
// `FormattedFields` is a formatted representation of the span's
|
||||
// fields, which is stored in its extensions by the `fmt` layer's
|
||||
// `new_span` method. The fields will have been formatted
|
||||
// by the same field formatter that's provided to the event
|
||||
// formatter in the `FmtContext`.
|
||||
let ext = span.extensions();
|
||||
let fields = &ext
|
||||
.get::<FormattedFields<N>>()
|
||||
.expect("will never be `None`");
|
||||
|
||||
// Skip formatting the fields if the span had no fields.
|
||||
if !fields.is_empty() {
|
||||
//write!(writer, "{{{}}}", fields)?;
|
||||
}
|
||||
write!(writer, ": ")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Write fields on the event
|
||||
ctx.field_format().format_fields(writer.by_ref(), event)?;
|
||||
|
||||
writeln!(writer);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// thanks: https://stackoverflow.com/a/75546824
|
||||
pub struct StringVisitor<'a> {
|
||||
string: &'a mut String,
|
||||
}
|
||||
impl<'a> StringVisitor<'a> {
|
||||
pub(crate) fn new(string: &'a mut String) -> Self {
|
||||
StringVisitor { string }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visit for StringVisitor<'a> {
|
||||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
||||
if field.name() == "message" {
|
||||
write!(self.string, "{value:?} ").unwrap();
|
||||
} else {
|
||||
write!(self.string, "{} = {:?}; ", field.name(), value).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,12 @@ use std::path::{Path, PathBuf};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt, Interest};
|
||||
use tokio::net::unix::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use tokio::net::{UnixListener as TokioUnixListener, UnixStream};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult};
|
||||
use crate::instance::connection::{ConnListener, Connection};
|
||||
use crate::instance::{self, Mize};
|
||||
use crate::proto::{self, MizeMessage};
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct UnixListener {
|
||||
sock_path: PathBuf,
|
||||
@@ -32,7 +32,7 @@ pub fn connect(instance: &mut Mize, store_path: PathBuf) -> MizeResult<()> {
|
||||
|
||||
let ns_of_peer_str = instance
|
||||
.get(format!(
|
||||
"inst/con_by_id/{}/peer/0/config/namespace",
|
||||
"inst/con_by_id/{}/peer/self/config/namespace",
|
||||
conn_id
|
||||
))?
|
||||
.value_string()?;
|
||||
@@ -91,7 +91,7 @@ async fn unix_listen(listener: UnixListener, mut instance: Mize) -> MizeResult<(
|
||||
// remove the file at sock_path if it already exists
|
||||
fs::remove_file(&listener.sock_path);
|
||||
|
||||
let listener = TokioUnixListener::bind(&listener.sock_path).mize_result_msg(format!(
|
||||
let listener = TokioUnixListener::bind(&listener.sock_path).mres_msg(format!(
|
||||
"Could not bind to unix socket at '{}'",
|
||||
listener.sock_path.display()
|
||||
))?;
|
||||
@@ -100,7 +100,7 @@ async fn unix_listen(listener: UnixListener, mut instance: Mize) -> MizeResult<(
|
||||
let (mut unix_sock, addr) = listener
|
||||
.accept()
|
||||
.await
|
||||
.mize_result_msg("Error while accepting Unix sock connection")?;
|
||||
.mres_msg("Error while accepting Unix sock connection")?;
|
||||
info!("new connection");
|
||||
|
||||
let (send_tx, send_rx) = unbounded::<MizeMessage>();
|
||||
@@ -147,7 +147,7 @@ fn unix_outgoing(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unix_incomming(
|
||||
pub fn unix_incomming(
|
||||
mut unix_read: OwnedReadHalf,
|
||||
mut instance: Mize,
|
||||
conn_id: u64,
|
||||
224
core/src/platform/os/windows_socket.rs
Normal file
224
core/src/platform/os/windows_socket.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
// This module is only compiled on Windows
|
||||
// It provides the same functionality as unix_socket.rs but using Windows named pipes
|
||||
|
||||
use ciborium::Value as CborValue;
|
||||
use flume::{unbounded, Receiver, Sender};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult};
|
||||
use crate::instance::connection::{ConnListener, Connection};
|
||||
use crate::instance::{self, Mize};
|
||||
use crate::proto::{self, MizeMessage};
|
||||
|
||||
pub struct WindowsListener {
|
||||
pipe_name: String,
|
||||
}
|
||||
|
||||
impl WindowsListener {
|
||||
pub fn new(store_path: PathBuf) -> MizeResult<WindowsListener> {
|
||||
// On Windows, we use a named pipe with a specific format
|
||||
// The format is \.\pipe\<name> for local pipes
|
||||
// We'll use the store path hash or name to create a unique pipe name
|
||||
let pipe_name = format!(
|
||||
"\\.\\pipe\\mize_{}",
|
||||
store_path.to_string_lossy().replace('\\', "_").replace('/', "_").replace(':', "_")
|
||||
);
|
||||
Ok(WindowsListener { pipe_name })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(instance: &mut Mize, store_path: PathBuf) -> MizeResult<()> {
|
||||
let conn_id = instance.spawn_async_blocking(
|
||||
"windows_connection",
|
||||
connect_async(instance.clone(), store_path),
|
||||
)?;
|
||||
|
||||
let ns_of_peer_str = instance
|
||||
.get(format!(
|
||||
"inst/con_by_id/{}/peer/self/config/namespace",
|
||||
conn_id
|
||||
))?
|
||||
.value_string()?;
|
||||
let ns_of_peer = instance.namespace_from_string(ns_of_peer_str)?;
|
||||
|
||||
info!(
|
||||
"connect_async ... ns_of_peer: {}",
|
||||
ns_of_peer.clone().as_string()
|
||||
);
|
||||
|
||||
instance.connection_set_namespace(conn_id, ns_of_peer.clone());
|
||||
instance.set_namespace(ns_of_peer);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn connect_async(mut instance: Mize, store_path: PathBuf) -> MizeResult<u64> {
|
||||
let pipe_name = format!(
|
||||
"\\.\\pipe\\mize_{}",
|
||||
store_path.to_string_lossy().replace('\\', "_").replace('/', "_").replace(':', "_")
|
||||
);
|
||||
|
||||
// Create the client synchronously
|
||||
let client = ClientOptions::new().open(&pipe_name)?;
|
||||
|
||||
// Wrap the client in an Arc<Mutex> so it can be shared between incoming and outgoing tasks
|
||||
let shared_client = Arc::new(Mutex::new(client));
|
||||
|
||||
let (send_tx, send_rx) = unbounded::<MizeMessage>();
|
||||
|
||||
let conn_id = instance.new_connection(send_tx)?;
|
||||
|
||||
let cloned_instance = instance.clone();
|
||||
let incoming_client = shared_client.clone();
|
||||
instance.spawn_background("incomming", move || {
|
||||
let result = windows_incomming(incoming_client, cloned_instance, conn_id);
|
||||
if let Err(err) = result {
|
||||
warn!("Connection closing because of EOF: {:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let outgoing_cloned_instance = instance.clone();
|
||||
let outgoing_client = shared_client.clone();
|
||||
instance.spawn_background("outgoing", move || {
|
||||
let result = windows_outgoing(outgoing_client, send_rx, outgoing_cloned_instance, conn_id);
|
||||
if let Err(err) = result {
|
||||
warn!("Connection closing because of EOF: {:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Ok(conn_id)
|
||||
}
|
||||
|
||||
impl ConnListener for WindowsListener {
|
||||
fn listen(self, mut instance: Mize) -> MizeResult<()> {
|
||||
instance.spawn_async("windows listen async", windows_listen(self, instance.clone()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn windows_listen(listener: WindowsListener, mut instance: Mize) -> MizeResult<()> {
|
||||
// Create the first server instance
|
||||
let mut server = ServerOptions::new()
|
||||
.first_pipe_instance(true)
|
||||
.create(&listener.pipe_name)?;
|
||||
|
||||
loop {
|
||||
// Wait for a client to connect
|
||||
server.connect().await.mres_msg("Error while accepting Windows named pipe connection")?;
|
||||
info!("new connection");
|
||||
|
||||
let (send_tx, send_rx) = unbounded::<MizeMessage>();
|
||||
let conn_id = instance.new_connection(send_tx)?;
|
||||
|
||||
// Take ownership of the current server (which is now connected)
|
||||
let connected_server = server;
|
||||
|
||||
// Create a new server for the next connection
|
||||
server = ServerOptions::new().create(&listener.pipe_name)?;
|
||||
|
||||
// Wrap the connected server in an Arc<Mutex> so it can be shared
|
||||
let shared_server = Arc::new(Mutex::new(connected_server));
|
||||
|
||||
let cloned_instance = instance.clone();
|
||||
let incoming_server = shared_server.clone();
|
||||
instance.spawn_background("incomming", move || {
|
||||
let result = windows_incomming(incoming_server, cloned_instance, conn_id);
|
||||
if let Err(err) = result {
|
||||
warn!("Connection closing because of EOF: {:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let outgoing_cloned_instance = instance.clone();
|
||||
let outgoing_server = shared_server.clone();
|
||||
instance.spawn_background("outgoing", move || {
|
||||
let result = windows_outgoing(outgoing_server, send_rx, outgoing_cloned_instance, conn_id);
|
||||
if let Err(err) = result {
|
||||
warn!("Connection closing because of EOF: {:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn windows_outgoing<W>(shared_pipe: Arc<Mutex<W>>, send_rx: Receiver<MizeMessage>, mut instance: Mize, conn_id: u64) -> MizeResult<()>
|
||||
where
|
||||
W: AsyncWriteExt + Unpin,
|
||||
{
|
||||
for msg in send_rx {
|
||||
debug!("windows outgoing got msg: {}", msg);
|
||||
let adapter = MyCiboriumWriter {
|
||||
inner: &mut *shared_pipe.blocking_lock(),
|
||||
instance: &mut instance,
|
||||
};
|
||||
let value = msg.value();
|
||||
ciborium::into_writer(&value, adapter)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn windows_incomming<R>(shared_pipe: Arc<Mutex<R>>, mut instance: Mize, conn_id: u64) -> MizeResult<()>
|
||||
where
|
||||
R: AsyncReadExt + Unpin,
|
||||
{
|
||||
loop {
|
||||
let adapter = MyCiboriumReader {
|
||||
inner: &mut *shared_pipe.blocking_lock(),
|
||||
instance: &mut instance,
|
||||
};
|
||||
let value: CborValue = ciborium::from_reader(adapter)?;
|
||||
if let CborValue::Integer(_) = value {
|
||||
break;
|
||||
}
|
||||
let msg = MizeMessage::new(value, conn_id);
|
||||
debug!("windows incomming got msg: {}", msg);
|
||||
instance.got_msg(msg);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct MyCiboriumWriter<'a, W> {
|
||||
inner: &'a mut W,
|
||||
instance: &'a mut Mize,
|
||||
}
|
||||
|
||||
impl<'a, W> ciborium_io::Write for MyCiboriumWriter<'a, W>
|
||||
where
|
||||
W: AsyncWriteExt + Unpin,
|
||||
{
|
||||
type Error = MizeError;
|
||||
fn write_all(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
let runtime = self.instance.runtime.lock()?;
|
||||
let _ = runtime.block_on(self.inner.write_all(data))?;
|
||||
Ok(())
|
||||
}
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
let handle = self.instance.async_get_handle();
|
||||
let _ = handle.block_on(self.inner.flush());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct MyCiboriumReader<'a, R> {
|
||||
inner: &'a mut R,
|
||||
instance: &'a mut Mize,
|
||||
}
|
||||
|
||||
impl<'a, R> ciborium_io::Read for MyCiboriumReader<'a, R>
|
||||
where
|
||||
R: AsyncReadExt + Unpin,
|
||||
{
|
||||
type Error = MizeError;
|
||||
fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
let handle = self.instance.async_get_handle();
|
||||
let _ = handle.block_on(self.inner.read_exact(data))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,17 @@ pub struct MizeMessage {
|
||||
pub conn_id: u64,
|
||||
}
|
||||
|
||||
impl MizeMessage {
|
||||
pub fn bytes(&self) -> Vec<u8> {
|
||||
let mut writer = Vec::new();
|
||||
ciborium::into_writer(&self.value, &mut writer).unwrap();
|
||||
writer
|
||||
}
|
||||
pub fn into_writer(&self, writer: &mut impl io::Write) -> MizeResult<()> {
|
||||
Ok(ciborium::into_writer(&self.value, writer)?)
|
||||
}
|
||||
}
|
||||
|
||||
// msg items
|
||||
static MSG_CMD: u16 = 1;
|
||||
static MSG_ID: u16 = 2;
|
||||
@@ -42,6 +53,7 @@ pub enum MessageCmd {
|
||||
UpdateRequest,
|
||||
GetSub,
|
||||
Sub,
|
||||
Custom(ItemData),
|
||||
}
|
||||
|
||||
impl MizeMessage {
|
||||
@@ -49,10 +61,28 @@ impl MizeMessage {
|
||||
MizeMessage { value, conn_id }
|
||||
}
|
||||
|
||||
pub fn new_custom<T: IntoItemData, U: IntoItemData>(
|
||||
custom_cmd: T,
|
||||
value: U,
|
||||
conn_id: u64,
|
||||
) -> MizeMessage {
|
||||
let value = value.into_item_data();
|
||||
let custom_cmd = custom_cmd.into_item_data();
|
||||
|
||||
let cmd = (CborValue::Integer(MSG_CMD.into()), custom_cmd.0);
|
||||
let data = (CborValue::Integer(MSG_DATA.into()), value.cbor().to_owned());
|
||||
let msg_value = CborValue::Map(vec![cmd, data]);
|
||||
|
||||
MizeMessage {
|
||||
value: msg_value,
|
||||
conn_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_get(id: MizeId, conn_id: u64) -> MizeMessage {
|
||||
let id_path = id
|
||||
.path()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|string| CborValue::Text(string.to_owned()))
|
||||
.collect();
|
||||
|
||||
@@ -69,7 +99,7 @@ impl MizeMessage {
|
||||
pub fn new_get_sub(id: MizeId, conn_id: u64) -> MizeMessage {
|
||||
let id_path = id
|
||||
.path()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|string| CborValue::Text(string.to_owned()))
|
||||
.collect();
|
||||
|
||||
@@ -86,7 +116,7 @@ impl MizeMessage {
|
||||
pub fn new_sub(id: MizeId, conn_id: u64) -> MizeMessage {
|
||||
let id_path = id
|
||||
.path()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|string| CborValue::Text(string.to_owned()))
|
||||
.collect();
|
||||
|
||||
@@ -113,7 +143,7 @@ impl MizeMessage {
|
||||
pub fn new_create_reply(id: MizeId, conn_id: u64) -> MizeMessage {
|
||||
let id_path = id
|
||||
.path()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|string| CborValue::Text(string.to_owned()))
|
||||
.collect();
|
||||
|
||||
@@ -130,7 +160,7 @@ impl MizeMessage {
|
||||
pub fn new_give(id: MizeId, data: ItemData, conn_id: u64) -> MizeMessage {
|
||||
let id_path = id
|
||||
.path()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|string| CborValue::Text(string.to_owned()))
|
||||
.collect();
|
||||
|
||||
@@ -148,7 +178,7 @@ impl MizeMessage {
|
||||
pub fn new_update_request(id: MizeId, data: ItemData, conn_id: u64) -> MizeMessage {
|
||||
let id_path = id
|
||||
.path()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|string| CborValue::Text(string.to_owned()))
|
||||
.collect();
|
||||
|
||||
@@ -166,7 +196,7 @@ impl MizeMessage {
|
||||
pub fn new_update(id: MizeId, data: ItemData, conn_id: u64) -> MizeMessage {
|
||||
let id_path = id
|
||||
.path()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|string| CborValue::Text(string.to_owned()))
|
||||
.collect();
|
||||
|
||||
@@ -194,7 +224,7 @@ impl MizeMessage {
|
||||
}
|
||||
};
|
||||
|
||||
//check if in this map there is a c, otherwise return err
|
||||
//check if in this map there is a cmd, otherwise return err
|
||||
let val_of_c = {
|
||||
let mut tmp_val = 0;
|
||||
for (key, val) in msg_as_map {
|
||||
@@ -204,6 +234,8 @@ impl MizeMessage {
|
||||
if key_int == &one {
|
||||
if let CborValue::Integer(val_int) = val {
|
||||
tmp_val = val_int.to_owned().into();
|
||||
} else {
|
||||
return Ok(MessageCmd::Custom(ItemData(val.to_owned())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,7 +246,7 @@ impl MizeMessage {
|
||||
tmp_val
|
||||
};
|
||||
|
||||
// match on value of c
|
||||
// match on value of cmd
|
||||
let cmd = match val_of_c {
|
||||
1 => MessageCmd::Get,
|
||||
2 => MessageCmd::Update,
|
||||
@@ -380,7 +412,7 @@ impl fmt::Display for MizeMessage {
|
||||
self.value
|
||||
.serialize(&mut serde_json::Serializer::pretty(display_writer))
|
||||
.map_err(|serde_err| std::fmt::Error)
|
||||
.mize_result_msg("serialize error")
|
||||
.mres_msg("serialize error")
|
||||
.inspect_err(|e| {
|
||||
e.clone().log();
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"workspace": [
|
||||
"./packages/ppc/platform/obsidian",
|
||||
"./packages/marts",
|
||||
"./packages/mize"
|
||||
"./apps/obsidian-plugin",
|
||||
"./marts"
|
||||
]
|
||||
}
|
||||
|
||||
36
deno.lock
generated
36
deno.lock
generated
@@ -995,7 +995,24 @@
|
||||
},
|
||||
"workspace": {
|
||||
"members": {
|
||||
"packages/marts": {
|
||||
"apps/obsidian-plugin": {
|
||||
"dependencies": [
|
||||
"jsr:@deno/esbuild-plugin@^1.2.1",
|
||||
"npm:@flowershow/remark-wiki-link@^3.4.0",
|
||||
"npm:builtin-modules@5",
|
||||
"npm:esbuild@~0.27.3",
|
||||
"npm:mdast@3",
|
||||
"npm:obsidian@^1.12.2",
|
||||
"npm:remark-frontmatter@5",
|
||||
"npm:remark-gfm@^4.0.1",
|
||||
"npm:remark-parse@11",
|
||||
"npm:remark-stringify@11",
|
||||
"npm:typescript@^5.9.3",
|
||||
"npm:unified@^11.0.5",
|
||||
"npm:unist-util-visit@^5.1.0"
|
||||
]
|
||||
},
|
||||
"marts": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@flowershow/remark-wiki-link@^3.4.0",
|
||||
@@ -1012,23 +1029,6 @@
|
||||
"npm:unist-util-visit@^5.1.0"
|
||||
]
|
||||
}
|
||||
},
|
||||
"packages/ppc/platform/obsidian": {
|
||||
"dependencies": [
|
||||
"jsr:@deno/esbuild-plugin@^1.2.1",
|
||||
"npm:@flowershow/remark-wiki-link@^3.4.0",
|
||||
"npm:builtin-modules@5",
|
||||
"npm:esbuild@~0.27.3",
|
||||
"npm:mdast@3",
|
||||
"npm:obsidian@^1.12.2",
|
||||
"npm:remark-frontmatter@5",
|
||||
"npm:remark-gfm@^4.0.1",
|
||||
"npm:remark-parse@11",
|
||||
"npm:remark-stringify@11",
|
||||
"npm:typescript@^5.9.3",
|
||||
"npm:unified@^11.0.5",
|
||||
"npm:unist-util-visit@^5.1.0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
mize → docs/architecture.md
Executable file → Normal file
0
mize → docs/architecture.md
Executable file → Normal file
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].
|
||||
615
docs/part-system.md
Normal file
615
docs/part-system.md
Normal file
@@ -0,0 +1,615 @@
|
||||
# Mize Part System (LLM generated guide)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### The MizePart Trait
|
||||
|
||||
All parts implement the `MizePart` trait, which provides:
|
||||
|
||||
```rust
|
||||
pub trait MizePart: MizePartGenerated {
|
||||
fn name(&self) -> &'static str; // Part identifier
|
||||
fn get_mize(&mut self) -> &mut Mize; // Access to the Mize instance
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()>; // Initialization
|
||||
fn run(&mut self, mize: &mut Mize) -> MizeResult<()>; // Execution
|
||||
fn deps(&self) -> &'static [&'static str]; // Dependencies
|
||||
fn opts(&self, mize: &mut Mize); // Config options
|
||||
// ... async variants
|
||||
}
|
||||
```
|
||||
|
||||
### Part Registration
|
||||
|
||||
Parts register themselves with the Mize instance using:
|
||||
|
||||
- **`mize.register_part(Box::new(MyPart { ... }))`** - For parts with state and methods
|
||||
- **`mize.add_name_only_part("name")`** - For simple parts that only need to be known by name (and have no state)
|
||||
|
||||
### Part Access
|
||||
|
||||
Parts can access other parts using:
|
||||
|
||||
```rust
|
||||
// Get a part by name (returns a guard that ensures the part is returned)
|
||||
let mut part = mize.get_part("part_name")?;
|
||||
|
||||
// Get a typed part (for parts with structs)
|
||||
let mut typed_part = mize.get_part_native::<OtherPart>("other_part")?;
|
||||
```
|
||||
|
||||
**Important**: Users must ensure that dependent parts are enabled before the parts that depend on them. There's no automatic dependency resolution.
|
||||
|
||||
---
|
||||
|
||||
## Current Implementation (Compiled-in Parts)
|
||||
|
||||
Currently, all parts are compiled into the `marts` crate and registered via the `compile_in_parts!` macro in the application's `main.rs`.
|
||||
|
||||
### Example: `apps/os/src/main.rs`
|
||||
|
||||
```rust
|
||||
compile_in_parts! {
|
||||
mize,
|
||||
"cli": marts::cli,
|
||||
"auth": marts::part_client_auth,
|
||||
"spacetime": marts::spacetime,
|
||||
"ppc.server": marts::ppc::server::part_ppc_server,
|
||||
"c2vi": marts::part_c2vi,
|
||||
"sci": marts::sci::part_sci,
|
||||
"sci.timetrack": marts::sci::timetrack::part_timetrack,
|
||||
"sci.inbox": marts::sci::inbox::part_sci_inbox,
|
||||
};
|
||||
```
|
||||
|
||||
Each entry maps a part name to a function that registers the part with the Mize instance.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. The macro reads the `enable` config option (a list of part names)
|
||||
2. For each enabled part, it calls the corresponding registration function
|
||||
3. Each registration function creates the part and calls `mize.register_part()`
|
||||
|
||||
---
|
||||
|
||||
## Creating a New Part
|
||||
|
||||
### Option 1: Simple Name-Only Part
|
||||
|
||||
For parts that don't need state or methods:
|
||||
|
||||
```rust
|
||||
// In your part module
|
||||
pub fn part_my_simple_part(mize: &mut Mize) -> MizeResult<()> {
|
||||
// Just register the name
|
||||
mize.add_name_only_part("my_simple_part");
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: Part with State and Methods (Using `mize_part` Macro)
|
||||
|
||||
For parts that need to store state and expose methods:
|
||||
|
||||
```rust
|
||||
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
||||
|
||||
#[mize_part] // or #[mize_part("custom_name")] for custom name
|
||||
#[derive(Default)]
|
||||
pub struct MyPart {
|
||||
mize: Mize, // Required field
|
||||
counter: u32, // Your custom state
|
||||
api_key: String, // More state
|
||||
}
|
||||
|
||||
// Implement MizePartCreate for parts that need custom initialization
|
||||
impl MizePartCreate for MyPart {
|
||||
// Optional: override create_generated for custom initialization
|
||||
}
|
||||
|
||||
impl MizePart for MyPart {
|
||||
// Optional: override name if not using the struct name
|
||||
fn name(&self) -> &'static str {
|
||||
"my_part" // Default would be "mypart" from struct name
|
||||
}
|
||||
|
||||
// Optional: define config options this part needs
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("my_part.api_key");
|
||||
mize.new_opt("my_part.counter_initial");
|
||||
}
|
||||
|
||||
// Optional: initialization logic
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
self.counter = mize.get_config("my_part.counter_initial")?.value_u32()?;
|
||||
self.api_key = mize.get_config("my_part.api_key")?.value_string()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Optional: define dependencies on other parts
|
||||
fn deps(&self) -> &'static [&'static str] {
|
||||
&["auth"] // This part requires "auth" to be loaded first
|
||||
}
|
||||
}
|
||||
|
||||
// The registration function
|
||||
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
|
||||
mize.register_part(Box::new(MyPart {
|
||||
mize: mize.clone(),
|
||||
counter: 0,
|
||||
api_key: String::new(),
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// You can add methods to your part struct
|
||||
impl MyPart {
|
||||
pub fn increment(&mut self) -> u32 {
|
||||
self.counter += 1;
|
||||
self.counter
|
||||
}
|
||||
|
||||
pub fn get_api_key(&self) -> &str {
|
||||
&self.api_key
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using the Part from Another Part
|
||||
|
||||
```rust
|
||||
pub fn part_another_part(mize: &mut Mize) -> MizeResult<()> {
|
||||
// Get the MyPart instance
|
||||
let mut my_part = mize.get_part_native::<MyPart>("my_part")?;
|
||||
|
||||
// Call its methods
|
||||
let count = my_part.increment();
|
||||
let api_key = my_part.get_api_key();
|
||||
|
||||
// ... your logic
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The `mize_part` Macro
|
||||
|
||||
The `#[mize_part]` procedural macro simplifies part creation by automatically implementing the required traits.
|
||||
|
||||
### What It Generates
|
||||
|
||||
For a struct like:
|
||||
|
||||
```rust
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct MyPart {
|
||||
mize: Mize,
|
||||
// ... your fields
|
||||
}
|
||||
```
|
||||
|
||||
The macro generates:
|
||||
|
||||
1. **`MizePartGenerated` implementation** - Provides default implementations for the generated trait methods
|
||||
2. **`MizePartCreateGenerated` implementation** - Provides a default `create_generated` method
|
||||
3. **`MizePartCreateThisSucks` implementation** - Compatibility trait
|
||||
4. **Extern "C" function** - Named `make_mize_part_<name>` for dynamic loading
|
||||
|
||||
### Custom Part Name
|
||||
|
||||
```rust
|
||||
#[mize_part("custom_name")]
|
||||
pub struct MyPart {
|
||||
mize: Mize,
|
||||
}
|
||||
```
|
||||
|
||||
This will use "custom_name" instead of the derived "mypart".
|
||||
|
||||
### Requirements
|
||||
|
||||
- The struct **must** have a field named `mize: Mize`
|
||||
- The struct should derive `Default` or implement it
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Defining Config Options
|
||||
(about to be deprecad, does nothing)
|
||||
|
||||
Parts declare what config options they need in their `opts()` method:
|
||||
|
||||
```rust
|
||||
impl MizePart for MyPart {
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("my_part.api_url");
|
||||
mize.new_opt("my_part.timeout");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Config Options
|
||||
|
||||
Parts retrieve config using `mize.get_config()`:
|
||||
|
||||
```rust
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
let api_url = mize.get_config("my_part.api_url")?.value_string()?;
|
||||
let timeout = mize.get_config("my_part.timeout")?.value_u64()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### User Configuration
|
||||
|
||||
Users configure parts via TOML files or environment variables. Example `config.toml`:
|
||||
|
||||
```toml
|
||||
[my_part]
|
||||
api_url = "https://api.example.com"
|
||||
timeout = 30
|
||||
|
||||
[enable]
|
||||
# List of parts to enable
|
||||
my_part = true
|
||||
auth = true
|
||||
spacetime = true
|
||||
```
|
||||
|
||||
Or via command line:
|
||||
|
||||
```bash
|
||||
mize --opt my_part.api_url=https://api.example.com run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part Dependencies
|
||||
|
||||
### Declaring Dependencies
|
||||
(about to be deprecad, does nothing)
|
||||
|
||||
Parts declare their dependencies in the `deps()` method:
|
||||
|
||||
```rust
|
||||
impl MizePart for MyPart {
|
||||
fn deps(&self) -> &'static [&'static str] {
|
||||
&["auth", "spacetime"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: This is currently informational only. Users must manually ensure dependencies are enabled.
|
||||
|
||||
### Accessing Dependencies
|
||||
|
||||
Use `get_part_native()` to access other parts:
|
||||
|
||||
```rust
|
||||
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
// Get the auth part
|
||||
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
||||
|
||||
// Use it
|
||||
let token = auth.id_token()?;
|
||||
|
||||
// Get spacetime
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Vision: Separate Crates
|
||||
|
||||
The goal is to move each part to its own crate following the naming convention **`mize-part-<name>`**. when creating a part make a new Cargo project like this already.
|
||||
|
||||
### Structure of a Part Crate
|
||||
|
||||
```
|
||||
mize-part-discord/
|
||||
├── Cargo.toml
|
||||
└── src/
|
||||
└── lib.rs
|
||||
```
|
||||
|
||||
### Example: `mize-part-discord/Cargo.toml`
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "mize-part-discord"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mize = { path = "../mize" }
|
||||
serenity = "0.12"
|
||||
```
|
||||
|
||||
### Example: `mize-part-discord/src/lib.rs`
|
||||
|
||||
```rust
|
||||
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
||||
use serenity::Client;
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct DiscordPart {
|
||||
mize: Mize,
|
||||
client: Option<Client>,
|
||||
}
|
||||
|
||||
impl MizePartCreate for DiscordPart {}
|
||||
|
||||
impl MizePart for DiscordPart {
|
||||
fn name(&self) -> &'static str {
|
||||
"discord"
|
||||
}
|
||||
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("discord.token");
|
||||
}
|
||||
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
let token = mize.get_config("discord.token")?.value_string()?;
|
||||
// Initialize serenity client...
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// The registration function - THIS IS WHAT MATTERS
|
||||
pub fn part_discord(mize: &mut Mize) -> MizeResult<()> {
|
||||
mize.register_part(Box::new(DiscordPart {
|
||||
mize: mize.clone(),
|
||||
client: None,
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Using Separate Part Crates
|
||||
|
||||
In your application's `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
mize = { path = "../mize" }
|
||||
mize-part-discord = { path = "../mize-part-discord" }
|
||||
mize-part-auth = { path = "../mize-part-auth" }
|
||||
```
|
||||
|
||||
In your application's `main.rs`:
|
||||
|
||||
```rust
|
||||
compile_in_parts! {
|
||||
mize,
|
||||
"discord": mize_part_discord::part_discord,
|
||||
"auth": mize_part_auth::part_auth,
|
||||
};
|
||||
```
|
||||
|
||||
## Complete Example: Creating a New Part
|
||||
|
||||
Let's create a complete example of a new part called `mize-part-greeting`.
|
||||
|
||||
### Step 1: Create the Crate
|
||||
|
||||
```bash
|
||||
cargo new --lib mize-part-greeting
|
||||
cd mize-part-greeting
|
||||
```
|
||||
|
||||
### Step 2: Edit `Cargo.toml`
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "mize-part-greeting"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mize = { path = "../mize" }
|
||||
```
|
||||
|
||||
### Step 3: Create the Part (`src/lib.rs`)
|
||||
|
||||
```rust
|
||||
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
||||
|
||||
/// A simple greeting part that stores a name and can generate greetings
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct GreetingPart {
|
||||
mize: Mize,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl MizePartCreate for GreetingPart {}
|
||||
|
||||
impl MizePart for GreetingPart {
|
||||
fn name(&self) -> &'static str {
|
||||
"greeting"
|
||||
}
|
||||
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("greeting.name");
|
||||
mize.new_opt("greeting.formal");
|
||||
}
|
||||
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
self.name = mize
|
||||
.get_config("greeting.name")?
|
||||
.value_string()
|
||||
.unwrap_or_else(|_| "World".to_string());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GreetingPart {
|
||||
/// Generate a greeting message
|
||||
pub fn greet(&self) -> String {
|
||||
let formal = self
|
||||
.mize
|
||||
.get_config("greeting.formal")
|
||||
.map(|v| v.value_bool().unwrap_or(false))
|
||||
.unwrap_or(false);
|
||||
|
||||
if formal {
|
||||
format!("Good day, {}!", self.name)
|
||||
} else {
|
||||
format!("Hello, {}!", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the name
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
|
||||
/// Get the current name
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
/// Registration function - this is what the compile_in_parts! macro will call
|
||||
pub fn part_greeting(mize: &mut Mize) -> MizeResult<()> {
|
||||
mize.register_part(Box::new(GreetingPart {
|
||||
mize: mize.clone(),
|
||||
name: String::new(),
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Add to Your Application
|
||||
|
||||
In your application's `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
mize = { path = "../mize" }
|
||||
mize-part-greeting = { path = "../mize-part-greeting" }
|
||||
```
|
||||
|
||||
In your application's `main.rs`:
|
||||
|
||||
```rust
|
||||
compile_in_parts! {
|
||||
mize,
|
||||
"greeting": mize_part_greeting::part_greeting,
|
||||
};
|
||||
```
|
||||
|
||||
### Step 5: Use the Part
|
||||
|
||||
```rust
|
||||
// In another part or in your CLI
|
||||
let mut greeting = mize.get_part_native::<GreetingPart>("greeting")?;
|
||||
println!("{}", greeting.greet());
|
||||
|
||||
// Change the name
|
||||
greeting.set_name("Alice".to_string());
|
||||
println!("{}", greeting.greet());
|
||||
```
|
||||
|
||||
### Step 6: Configure It
|
||||
|
||||
Create a `config.toml`:
|
||||
|
||||
```toml
|
||||
[greeting]
|
||||
name = "Bob"
|
||||
formal = true
|
||||
|
||||
[enable]
|
||||
greeting = true
|
||||
```
|
||||
|
||||
Run with:
|
||||
|
||||
```bash
|
||||
mize --config config.toml run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Naming**: Use `mize-part-<name>` for crate names and `part_<name>` for registration functions
|
||||
2. **State**: Store part-specific state in the part struct
|
||||
3. **Dependencies**: Declare dependencies in `deps()` and document them
|
||||
4. **Config**: Declare all config options in `opts()`
|
||||
5. **Error Handling**: Use `MizeResult` and `mize_err!` for errors
|
||||
6. **Documentation**: Document your part's purpose, config options, and methods
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
- Parts are modular units of functionality in Mize
|
||||
- Each part implements the `MizePart` trait
|
||||
- Use `#[mize_part]` macro for parts with state and methods
|
||||
- Register parts with `mize.register_part()` or `mize.add_name_only_part()`
|
||||
- Parts access config via `mize.get_config()`
|
||||
- Parts access other parts via `mize.get_part_native::<T>()`
|
||||
- Users enable parts via the `enable` config option
|
||||
- Future: Each part will be its own crate named `mize-part-<name>`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Common Patterns
|
||||
|
||||
**Register a part:**
|
||||
```rust
|
||||
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
|
||||
```
|
||||
|
||||
**Get a part:**
|
||||
```rust
|
||||
let mut part = mize.get_part_native::<MyPart>("my_part")?;
|
||||
```
|
||||
|
||||
**Get config:**
|
||||
```rust
|
||||
let value = mize.get_config("my_part.setting")?.value_string()?;
|
||||
```
|
||||
|
||||
**Declare config option:**
|
||||
```rust
|
||||
mize.new_opt("my_part.setting");
|
||||
```
|
||||
|
||||
**Define a part struct:**
|
||||
```rust
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct MyPart {
|
||||
mize: Mize,
|
||||
// your fields...
|
||||
}
|
||||
```
|
||||
|
||||
**Registration function:**
|
||||
```rust
|
||||
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
|
||||
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
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`.
|
||||
6
flake.lock
generated
6
flake.lock
generated
@@ -72,11 +72,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1774709303,
|
||||
"narHash": "sha256-D3Q07BbIA2KnTcSXIqqu9P586uWxN74zNoCH3h2ESHg=",
|
||||
"lastModified": 1782467914,
|
||||
"narHash": "sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685",
|
||||
"rev": "e73de5be04e0eff4190a1432b946d469c794e7b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
439
flake.nix
439
flake.nix
@@ -1,6 +1,5 @@
|
||||
{
|
||||
inputs = {
|
||||
# nixpkgs 35.11 still contains rust 1.73
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
fenix = {
|
||||
@@ -18,7 +17,7 @@
|
||||
};
|
||||
|
||||
mize_modules = {
|
||||
url = "github:c2vi/mize-modules?submodules=1";
|
||||
url = "github:c2vi/mize-modules";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
@@ -29,213 +28,148 @@
|
||||
############################## LET BINDINGS ##############################
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
wasmToolchain = fenix.packages.${system}.combine [
|
||||
fenix.packages.${system}.targets.wasm32-unknown-unknown.stable.toolchain
|
||||
fenix.packages.${system}.stable.toolchain
|
||||
];
|
||||
wasmCrane = (crane.mkLib pkgs).overrideToolchain wasmToolchain;
|
||||
|
||||
osToolchain = fenix.packages.${system}.nightly.toolchain;
|
||||
|
||||
osToolchain = fenix.packages.${system}.latest.toolchain;
|
||||
osCrane = (crane.mkLib pkgs).overrideToolchain osToolchain;
|
||||
|
||||
#winToolchain = fenix.packages.${system}.combine [
|
||||
#fenix.packages.${system}.targets.x86_64-pc-windows-gnu.latest.toolchain
|
||||
#fenix.packages.${system}.latest.toolchain
|
||||
#];
|
||||
winToolchain = with fenix.packages.${system};
|
||||
combine [
|
||||
minimal.rustc
|
||||
minimal.cargo
|
||||
targets.x86_64-pc-windows-gnu.latest.rust-std
|
||||
];
|
||||
winCrane = (crane.mkLib pkgs).overrideToolchain winToolchain;
|
||||
|
||||
wasmArtifacts = wasmCrane.buildDepsOnly ({
|
||||
src = self;
|
||||
doCheck = false; # tests does not work in wasm
|
||||
CARGO_BUILD_TARGET = "wasm32-unknown-unknown";
|
||||
cargoExtraArgs = "--features target-wasm --no-default-features";
|
||||
RUSTFLAGS="-C linker=wasm-ld";
|
||||
buildInputs = with pkgs; [ cargo-binutils lld ];
|
||||
});
|
||||
|
||||
defaultMizeConfig = {
|
||||
config = {
|
||||
# mize build time config
|
||||
mizeBuildConfig = {
|
||||
namespace = "mize.buildtime.ns";
|
||||
module_url = "c2vi.dev";
|
||||
};
|
||||
module_url = "parts.mize.works";
|
||||
};
|
||||
|
||||
#systems = [ "aarch64-linux" "x86_64-linux" "x86_64-pc-windows-gnu" "wasm32-unknown-none-unknown" "x86_64-apple-darwin" "aarch64-apple-darwin" ];
|
||||
systems = [ "aarch64-unknown-linux-gnu" "x86_64-unknown-linux-gnu" "x86_64-pc-windows-gnu" "wasm32-unknown-none-unknown" ];
|
||||
|
||||
mizeLib = import ./lib.nix {
|
||||
inherit inputs nixpkgs pkgs self osCrane defaultMizeConfig mize_modules;
|
||||
inherit rust-overlay crane fenix;
|
||||
buildSystem = system;
|
||||
stdenv = pkgs.stdenv;
|
||||
};
|
||||
|
||||
in {
|
||||
############################## PACKAGES ##############################
|
||||
|
||||
packages = rec {
|
||||
|
||||
inherit osCrane;
|
||||
#test = mizeLib.buildMizeForSystem "wasm32-unknown-none-unknown";
|
||||
test = mizeLib.buildMizeForSystem "aarch64-linux";
|
||||
#test = mizeLib.buildMizeForSystem "x86_64-pc-windows-gnu";
|
||||
|
||||
mizeFor = let
|
||||
mizes = map mizeLib.buildMizeForSystem systems;
|
||||
in builtins.listToAttrs ( map ( mize: { name = mize.system.name; value = mize; } ) mizes );
|
||||
|
||||
dist = mizeLib.dist systems;
|
||||
|
||||
random = mizeLib.random systems;
|
||||
|
||||
inherit mizeLib;
|
||||
|
||||
|
||||
#pkgsCross = import nixpkgs { buildSystem = system; crossSystem = { config = "x86_64-pc-windows-gnu"; }; overlays = [ rust-overlay.overlays.default ]; };
|
||||
|
||||
pkgsTest = pkgs;
|
||||
|
||||
#pkgsCross = import nixpkgs { buildSystem = system; crossSystem = { config = "x86_64-w64-windows-mingw"; }; overlays = [ rust-overlay.overlays.default ]; };
|
||||
pkgsCross = import nixpkgs { buildSystem = system; crossSystem = { config = "x86_64-unknown-linux-gnu"; }; overlays = [ rust-overlay.overlays.default ]; };
|
||||
craneLib = (crane.mkLib pkgsCross).overrideToolchain (p: p.rust-bin.stable.latest.default);
|
||||
|
||||
|
||||
one = craneLib.buildPackage {
|
||||
src = "${self}";
|
||||
#cargoExtraArgs = "--bin mize --features target-os";
|
||||
cargoExtraArgs = "--bin mize";
|
||||
doCheck = false; # tests does not work in wasm
|
||||
CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu";
|
||||
MIZE_BUILD_CONFIG = pkgs.writeTextFile {
|
||||
name = "mize-build-config";
|
||||
text = builtins.toJSON (defaultMizeConfig // {
|
||||
mize_version = one.version;
|
||||
});
|
||||
};
|
||||
fileSetForCrate =
|
||||
crate:
|
||||
pkgs.lib.fileset.toSource {
|
||||
root = ./.;
|
||||
fileset = pkgs.lib.fileset.unions [
|
||||
./Cargo.toml
|
||||
./Cargo.lock
|
||||
(osCrane.fileset.commonCargoSources ./packages/marts)
|
||||
(osCrane.fileset.commonCargoSources ./packages/mize)
|
||||
(osCrane.fileset.commonCargoSources ./packages/ppc/spacetimedb)
|
||||
(osCrane.fileset.commonCargoSources ./packages/mme)
|
||||
(osCrane.fileset.commonCargoSources ./packages/vic)
|
||||
(osCrane.fileset.commonCargoSources ./packages/ac_mize_macros)
|
||||
(osCrane.fileset.commonCargoSources crate)
|
||||
];
|
||||
};
|
||||
|
||||
default = osCrane.buildPackage {
|
||||
src = "${self}";
|
||||
cargoExtraArgs = "--bin mize --features target-os";
|
||||
ppc = osCrane.buildPackage {
|
||||
src = fileSetForCrate ./packages/ppc;
|
||||
cargoExtraArgs = "-p ppc";
|
||||
nativeBuildInputs = [
|
||||
pkgs.pkg-config
|
||||
pkgs.gobject-introspection
|
||||
pkgs.gobject-introspection.dev
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
git # needed to build spacetimedb as it needs to get it's git hash for setting it's version
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
pango
|
||||
webkitgtk_4_1
|
||||
openssl
|
||||
wasm-bindgen-cli
|
||||
lld_20
|
||||
xdotool
|
||||
];
|
||||
|
||||
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
|
||||
xdotool
|
||||
webkitgtk_4_1
|
||||
pango
|
||||
openssl
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
];
|
||||
};
|
||||
mize = osCrane.buildPackage {
|
||||
src = fileSetForCrate ./packages/mize;
|
||||
cargoExtraArgs = "-p mize";
|
||||
nativeBuildInputs = [
|
||||
pkgs.pkg-config
|
||||
pkgs.gobject-introspection
|
||||
pkgs.gobject-introspection.dev
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
git # needed to build spacetimedb as it needs to get it's git hash for setting it's version
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
pango
|
||||
webkitgtk_4_1
|
||||
openssl
|
||||
wasm-bindgen-cli
|
||||
lld_20
|
||||
xdotool
|
||||
];
|
||||
|
||||
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
|
||||
xdotool
|
||||
webkitgtk_4_1
|
||||
pango
|
||||
openssl
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
];
|
||||
};
|
||||
|
||||
wasm = wasmCrane.buildPackage {
|
||||
src = "${self}";
|
||||
CARGO_BUILD_TARGET = "wasm32-unknown-unknown";
|
||||
cargoExtraArgs = "--bin mize --features target-wasm ";
|
||||
doCheck = false;
|
||||
};
|
||||
inherit wasmArtifacts;
|
||||
|
||||
# Thanks to:
|
||||
# - https://github.com/dl-solarity/hardhat-diamond-tools/blob/e5b8bc9624fbf89eda83a8ce4f6b6672ea83f550/flake.nix#L103
|
||||
# - https://github.com/ipetkov/crane/issues/362#issuecomment-1683220603
|
||||
npmPackage = wasmCrane.mkCargoDerivation {
|
||||
name = "mize-npm-package";
|
||||
|
||||
# i can't use CARGO_BUILD_TARGET to set the target
|
||||
# because then the cargo install run by wasm-pack tries to build the wasm-bindgen-cli for a wasm target...
|
||||
|
||||
|
||||
src = self;
|
||||
|
||||
cargoArtifacts = wasmArtifacts;
|
||||
doCheck = false;
|
||||
|
||||
buildPhaseCargoCommand = ''
|
||||
echo build hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
|
||||
mkdir -p $out/pkg
|
||||
|
||||
export HOME=$(mktemp -d fake-homeXXXX)
|
||||
wasm-pack build --out-dir $out/pkg --scope=c2vi -- --no-default-features --features target-wasm
|
||||
'';
|
||||
|
||||
buildInputs = with pkgs; [ wasm-bindgen-cli binaryen wasm-pack wasmToolchain ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
############################## DEV SHELLS ##############################
|
||||
devShells = {
|
||||
|
||||
modules = mizeLib.moduleShells system;
|
||||
# shell without spacetime and other dev commands. just all needed to build mize or ppc
|
||||
buildShell = pkgs.mkShell {
|
||||
|
||||
modulesCross = pkgs.lib.genAttrs systems (system: mizeLib.moduleShells system);
|
||||
|
||||
one = pkgs.stdenv.mkDerivation {
|
||||
name = "hiiiiiiii";
|
||||
nativeBuildInputs = [
|
||||
(fenix.packages.${system}.combine [
|
||||
fenix.packages.${system}.targets.wasm32-unknown-unknown.latest.toolchain
|
||||
fenix.packages.${system}.latest.toolchain
|
||||
fenix.packages.${system}.latest.rust-src
|
||||
])
|
||||
];
|
||||
};
|
||||
|
||||
win = winCrane.buildPackage {
|
||||
#src = winCrane.cleanCargoSource ./testing;
|
||||
#pname = "testing";
|
||||
src = ./testing;
|
||||
|
||||
strictDeps = true;
|
||||
doCheck = false;
|
||||
|
||||
CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu";
|
||||
|
||||
# fixes issues related to libring
|
||||
TARGET_CC = "${pkgs.pkgsCross.mingwW64.stdenv.cc}/bin/${pkgs.pkgsCross.mingwW64.stdenv.cc.targetPrefix}cc";
|
||||
|
||||
#fixes issues related to openssl
|
||||
OPENSSL_DIR = "${pkgs.openssl.dev}";
|
||||
OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib";
|
||||
OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include/";
|
||||
|
||||
depsBuildBuild = with pkgs; [
|
||||
pkgsCross.mingwW64.stdenv.cc
|
||||
pkgsCross.mingwW64.windows.pthreads
|
||||
];
|
||||
shellHook = ''
|
||||
export MIZE_CONFIG_FILE=${self}/test-config.toml
|
||||
'';
|
||||
MIZE_BUILD_CONFIG = pkgs.writeTextFile {
|
||||
name = "vic-build-config";
|
||||
text = builtins.toJSON defaultMizeConfig;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
#default = (mizeLib.buildMizeForSystem system).modules.mize.devShell;
|
||||
default = pkgs.mkShell {
|
||||
|
||||
MIZE_BUILD_CONFIG = let
|
||||
settingsFormat = pkgs.formats.toml { };
|
||||
in settingsFormat.generate "mize-build-config.toml" {
|
||||
config = {
|
||||
namespace = "mize.buildtime.ns";
|
||||
module_url = "c2vi.dev";
|
||||
};
|
||||
};
|
||||
MIZE_BUILD_CONFIG = builtins.toJSON mizeBuildConfig;
|
||||
shellHook = ''
|
||||
mize_project_root=$(${pkgs.lib.getExe pkgs.git} rev-parse --show-toplevel)
|
||||
export MIZE_CONFIG_FILES=$mize_project_root/mize_config.toml
|
||||
'';
|
||||
#_shell_type = "rust";
|
||||
nativeBuildInputs = [
|
||||
pkgs.wasm-pack
|
||||
pkgs.dioxus-cli
|
||||
pkgs.spacetimedb
|
||||
pkgs.deno
|
||||
pkgs.pkg-config
|
||||
pkgs.gobject-introspection
|
||||
pkgs.gobject-introspection.dev
|
||||
pkgs.deno
|
||||
|
||||
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
|
||||
(pkgs.buildWasmBindgenCli rec {
|
||||
@@ -292,12 +226,157 @@ in {
|
||||
];
|
||||
|
||||
};
|
||||
default = pkgs.mkShell rec {
|
||||
|
||||
MIZE_BUILD_CONFIG = builtins.toJSON mizeBuildConfig;
|
||||
shellHook = ''
|
||||
mize_project_root=$(${pkgs.lib.getExe pkgs.git} rev-parse --show-toplevel)
|
||||
'';
|
||||
#_shell_type = "rust";
|
||||
nativeBuildInputs = [
|
||||
pkgs.wasm-pack
|
||||
pkgs.dioxus-cli
|
||||
pkgs.spacetimedb
|
||||
pkgs.deno
|
||||
pkgs.pkg-config
|
||||
pkgs.gobject-introspection
|
||||
pkgs.gobject-introspection.dev
|
||||
pkgs.systemd
|
||||
pkgs.rsync
|
||||
pkgs.openssh
|
||||
pkgs.cmake
|
||||
|
||||
# to build ../grid
|
||||
pkgs.rustPlatform.bindgenHook
|
||||
pkgs.pipewire.dev
|
||||
pkgs.alsa-lib.dev
|
||||
|
||||
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
|
||||
(pkgs.buildWasmBindgenCli rec {
|
||||
src = pkgs.fetchCrate {
|
||||
pname = "wasm-bindgen-cli";
|
||||
version = "0.2.114";
|
||||
hash = "sha256-xrCym+rFY6EUQFWyWl6OPA+LtftpUAE5pIaElAIVqW0=";
|
||||
};
|
||||
|
||||
}) // {
|
||||
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
|
||||
inherit src;
|
||||
inherit (src) pname version;
|
||||
hash = "sha256-Z8+dUXPQq7S+Q7DWNr2Y9d8GMuEdSnq00quUR0wDNPM=";
|
||||
};
|
||||
})
|
||||
(fenix.packages.${system}.combine [
|
||||
fenix.packages.${system}.latest.toolchain
|
||||
fenix.packages.${system}.targets.wasm32-unknown-unknown.latest.toolchain
|
||||
])
|
||||
];
|
||||
|
||||
############################## SOME GLOBAL OUTPUTS ##############################
|
||||
buildInputs = with pkgs; [
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
pango
|
||||
webkitgtk_4_1
|
||||
openssl
|
||||
wasm-bindgen-cli
|
||||
lld_20
|
||||
xdotool
|
||||
bzip2
|
||||
];
|
||||
|
||||
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
|
||||
xdotool
|
||||
webkitgtk_4_1
|
||||
pango
|
||||
openssl
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
|
||||
# to build ../grid
|
||||
libxkbcommon
|
||||
alsa-lib
|
||||
libgcc.lib
|
||||
libxcb
|
||||
pipewire
|
||||
wayland
|
||||
|
||||
# for rust-analyzer to work for zed
|
||||
bzip2
|
||||
];
|
||||
|
||||
LIBRARY_PATH = LD_LIBRARY_PATH;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
}) // rec {
|
||||
|
||||
############################## SOME GLOBAL OUTPUTS ##############################
|
||||
inherit inputs self;
|
||||
rustPkgs = import nixpkgs { overlays = [rust-overlay.overlays.default]; system = "x86_64-linux"; };
|
||||
|
||||
############################## NIXOS MODULES ##############################
|
||||
nixosModules.default = { system, lib, config, pkgs, ... }: {
|
||||
options.mize = {
|
||||
enable = lib.mkEnableOption "enable the mize damon";
|
||||
debugBinary = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/home/user/here/mize";
|
||||
description = "If set to NOT null, the mize daemon service will use this path instead of $${self.packages.system.mize}/bin/mize''";
|
||||
};
|
||||
debugConfigFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/home/user/here/mize.toml";
|
||||
description = "If set to NOT null, the mize daemon service will use the config file at this path instead of one written to the nix store";
|
||||
};
|
||||
enableConfigFile = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "allows to disable setting MIZE_CONFIG_FILES env var for the mize daemon service";
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = {};
|
||||
example = {
|
||||
dat_dir = "home/user/my_mize_data";
|
||||
spacetime.url = "spacetime.example.com";
|
||||
};
|
||||
description = "mize config for the daemon";
|
||||
};
|
||||
|
||||
};
|
||||
config = lib.mkIf config.mize.enable {
|
||||
systemd.user.services.mize = {
|
||||
description = "the mize daemon";
|
||||
wantedBy = [ "default.target" ];
|
||||
environment = lib.mkIf config.mize.enableConfigFile {
|
||||
MIZE_CONFIG_FILES = let fileInStore = (pkgs.formats.toml {}).generate "mize-daemon-config.toml" config.mize.config; in if config.mize.debugConfigFile == null then "${fileInStore}" else "${config.mize.debugConfigFile}";
|
||||
};
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
ExecStart = if config.mize.debugBinary == null then "${self.packages.${system}.mize}/bin/mize run" else "${config.mize.debugBinary} run";
|
||||
#After = [ "graphical-session.target" ];
|
||||
PartOf = [ "graphical-session.target" ]; # this should mean DISPLAY and WAYLAND_DISPLAY are set in the service env...
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
# for what we do the nixos and home-manager api is identical
|
||||
homeModules.default = nixosModules.default;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
445
lib.nix
445
lib.nix
@@ -1,445 +0,0 @@
|
||||
{ pkgs
|
||||
, osCrane
|
||||
, self
|
||||
, defaultMizeConfig
|
||||
, mize_modules
|
||||
, buildSystem
|
||||
, nixpkgs
|
||||
, rust-overlay
|
||||
, crane
|
||||
, fenix
|
||||
, stdenv
|
||||
, lib ? pkgs.lib
|
||||
, ...
|
||||
}:
|
||||
rec {
|
||||
|
||||
|
||||
|
||||
|
||||
getCrossSystem = system: let
|
||||
hostSystem = (if builtins.isString system then (pkgs.lib.systems.parse.mkSystemFromString system) else system)
|
||||
// rec {
|
||||
name = if hostSystem.vendor != "unknown"
|
||||
then "${hostSystem.cpu.name}-${hostSystem.kernel.name}-${hostSystem.abi.name}"
|
||||
else "${hostSystem.cpu.name}-${hostSystem.vendor.name}-${hostSystem.kernel.name}-${hostSystem.abi.name}";
|
||||
nameFull = "${hostSystem.cpu.name}-${hostSystem.vendor.name}-${hostSystem.kernel.name}-${hostSystem.abi.name}";
|
||||
nameRust = nameFull;
|
||||
config = nameFull;
|
||||
isCross = system != buildSystem;
|
||||
isNative = system == buildSystem;
|
||||
};
|
||||
in hostSystem;
|
||||
|
||||
|
||||
# get a list of all modules
|
||||
# aka folders with a mize.nix in them
|
||||
module_list_drv = path: pkgs.stdenv.mkDerivation {
|
||||
name = "mize_module_list";
|
||||
dontUnpack = true;
|
||||
configurePhase = "";
|
||||
buildPhase = ''
|
||||
mkdir -p $out
|
||||
touch $out/modules_to_build
|
||||
|
||||
find ${path} -not -type d -name mize.nix | while read p; do
|
||||
(echo "$p" >> $out/modules_to_build; echo "found module $p")
|
||||
done
|
||||
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
|
||||
findModules = path: let
|
||||
mize_module_no_externals = builtins.getEnv "MIZE_MODULE_NO_EXTERNALS";
|
||||
module_list_string = builtins.readFile "${module_list_drv path}/modules_to_build";
|
||||
module_list = pkgs.lib.lists.remove "" (pkgs.lib.strings.splitString "\n" module_list_string);
|
||||
getExternals = path: map findModules ((pkgs.callPackage ((import path).externals or (args: [])) {}));
|
||||
in module_list ++ (if mize_module_no_externals != "" then [] else (map getExternals module_list));
|
||||
|
||||
|
||||
modulesFileList = let
|
||||
from_mize_modules_repo = findModules mize_modules;
|
||||
mize_module_no_repo = builtins.getEnv "MIZE_MODULE_NO_REPO";
|
||||
in pkgs.lib.lists.flatten (from_env_var ++ (if mize_module_no_repo != "" then [] else from_mize_modules_repo) ++ (findModules self));
|
||||
|
||||
mize_module_path = builtins.getEnv "MIZE_MODULE_PATH";
|
||||
dirs_from_path = pkgs.lib.lists.remove "" (pkgs.lib.strings.splitString ":" mize_module_path);
|
||||
dirs_in_nix_store = map (path: builtins.fetchGit {
|
||||
url = path;
|
||||
}) dirs_from_path;
|
||||
from_env_var = map findModules dirs_in_nix_store;
|
||||
|
||||
|
||||
|
||||
buildMizeForSystem = system: let
|
||||
hostSystem = getCrossSystem system;
|
||||
|
||||
pkgsCross = import nixpkgs { inherit buildSystem hostSystem; localSystem = buildSystem; overlays = [ rust-overlay.overlays.default ]; };
|
||||
|
||||
pkgsNative = import nixpkgs { inherit buildSystem; overlays = [ rust-overlay.overlays.default ]; };
|
||||
|
||||
|
||||
|
||||
|
||||
craneLib =
|
||||
if hostSystem.kernel.name == "windows"
|
||||
then
|
||||
(crane.mkLib pkgs).overrideToolchain (fenix.packages.${buildSystem}.combine [
|
||||
#fenix.packages.${buildSystem}.minimal.rustc
|
||||
#fenix.packages.${buildSystem}.minimal.cargo
|
||||
fenix.packages.${buildSystem}.nightly.toolchain
|
||||
fenix.packages.${buildSystem}.targets."${hostSystem.cpu.name}-pc-windows-gnu".stable.toolchain
|
||||
])
|
||||
|
||||
else if hostSystem.cpu.name == "wasm32"
|
||||
then
|
||||
(crane.mkLib pkgs).overrideToolchain (fenix.packages.${buildSystem}.combine [
|
||||
fenix.packages.${buildSystem}.targets.wasm32-unknown-unknown.latest.toolchain
|
||||
fenix.packages.${buildSystem}.latest.toolchain
|
||||
])
|
||||
else if hostSystem.name == "x86_64-linux-gnu"
|
||||
then
|
||||
builtins.trace "x86_64-linux-gnu hackfix...."
|
||||
(crane.mkLib pkgs).overrideToolchain (fenix.packages.${buildSystem}.combine [
|
||||
fenix.packages.${buildSystem}.stable.toolchain
|
||||
])
|
||||
|
||||
else
|
||||
(crane.mkLib pkgsCross).overrideToolchain (p: fenix.packages.${buildSystem}.stable.toolchain)
|
||||
;
|
||||
|
||||
toolchain_version_drv = pkgs.stdenv.mkDerivation {
|
||||
name = "toolchain_version_drv";
|
||||
dontUnpack = true;
|
||||
configurePhase = "";
|
||||
buildPhase = ''
|
||||
mkdir -p $out
|
||||
touch $out/rustc-version
|
||||
|
||||
echo hiiiiiiiiiiiiiiiiiiiiiiii
|
||||
#echo 'rustc 1.80.1 (3f5fd8dd4 2024-08-06)' > $out/rustc-version
|
||||
${craneLib.cargo}/bin/rustc --version > $out/rustc-version
|
||||
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
|
||||
mkSelString = attrs: builtins.toJSON (attrs // {
|
||||
inherit toolchain_version;
|
||||
system = hostSystem.name;
|
||||
mize_version = "0.0.1";
|
||||
});
|
||||
|
||||
|
||||
toolchain_version = pkgs.lib.strings.removeSuffix "\n" (builtins.readFile "${toolchain_version_drv}/rustc-version");
|
||||
|
||||
|
||||
mkMizeModule = attrs: (if builtins.hasAttr "drvFunc" attrs then attrs.drvFunc else pkgsCross.stdenv.mkDerivation) ( rec {
|
||||
name = attrs.modName;
|
||||
inherit (attrs) modName;
|
||||
selector_string = mkSelString (attrs.select or {} // {
|
||||
inherit (attrs) modName;
|
||||
});
|
||||
hash = builtins.substring 0 32 (builtins.hashString "sha256" selector_string);
|
||||
}
|
||||
// (lib.attrsets.removeAttrs attrs ["drvFunc"] )
|
||||
);
|
||||
|
||||
|
||||
|
||||
########## build a Rust Module
|
||||
mkMizeRustModule = attrs: mkMizeModule (
|
||||
# general stuff
|
||||
{
|
||||
drvFunc = craneLib.buildPackage;
|
||||
MIZE_BUILD_CONFIG = mizeBuildConfigStr;
|
||||
mizeInstallPhase = attrs.mizeInstallPhase or ''
|
||||
mkdir -p $out/lib/
|
||||
cp $build_dir/target/${hostSystem.nameRust}/$debugOrRelease/libmize_module_${attrs.modName}.so $out/lib/
|
||||
'';
|
||||
mizeBuildPhase = attrs.mizeBuildPhase or ''
|
||||
cargo --color always build --target ${hostSystem.nameRust} ${if builtins.hasAttr "cargoExtraArgs" attrs then attrs.cargoExtraArgs else ""} --manifest-path $build_dir/Cargo.toml --lib
|
||||
'';
|
||||
selector_string = mkSelString (attrs.select or {} // {
|
||||
inherit (attrs) modName;
|
||||
});
|
||||
devShell = if builtins.hasAttr "devShell" attrs then attrs.devShell else mkMizeRustShell {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
# linux specific stuff
|
||||
// (lib.attrsets.optionalAttrs (hostSystem.kernel.name == "linux") {
|
||||
# rename the so library, in case the lib.name in your Cargo.toml is not like mize_module_mylib
|
||||
postInstall = (attrs.postInstall or "") + ''
|
||||
if [ ! -f "$out/lib/libmize_module_${attrs.modName}" ]; then
|
||||
echo RENAMING $out/lib/${attrs.modName}.so to $out/lib/libmize_module_${attrs.modName}.so
|
||||
mv $out/lib/${attrs.modName}.so to $out/lib/libmize_module_${attrs.modName}.so || true
|
||||
|
||||
echo RENAMING $out/lib/${attrs.modName}.a to $out/lib/libmize_module_${attrs.modName}.a
|
||||
mv $out/lib/${attrs.modName}.a to $out/lib/libmize_module_${attrs.modName}.a && true # ignore if there is no .a file || true
|
||||
echo "$FILE"
|
||||
fi
|
||||
'';
|
||||
})
|
||||
|
||||
|
||||
# linux cross stuff
|
||||
// (lib.attrsets.optionalAttrs (hostSystem.kernel.name == "linux" && hostSystem.isCross) {
|
||||
"CARGO_TARGET_${builtins.replaceStrings ["-"] ["_"] (pkgsCross.lib.strings.toUpper hostSystem.nameFull)}_LINKER" = "${pkgsCross.stdenv.cc.targetPrefix}cc";
|
||||
HOST_CC = "${pkgsCross.stdenv.cc.nativePrefix}cc";
|
||||
TARGET_CC = "${pkgsCross.stdenv.cc.targetPrefix}cc";
|
||||
CARGO_BUILD_TARGET = hostSystem.nameFull;
|
||||
nativeBuildInputs = attrs.nativeBuildInputs or [] ++ [
|
||||
pkgsCross.stdenv.cc
|
||||
];
|
||||
})
|
||||
|
||||
# wasm stuff
|
||||
// (lib.attrsets.optionalAttrs (hostSystem.cpu.name == "wasm32") {
|
||||
CARGO_BUILD_TARGET = "wasm32-unknown-unknown";
|
||||
|
||||
# checks fail on wasm
|
||||
doCheck = false;
|
||||
})
|
||||
|
||||
|
||||
# add windows stuff
|
||||
// (lib.attrsets.optionalAttrs (hostSystem.kernel.name == "windows") {
|
||||
strictDeps = true;
|
||||
doCheck = false;
|
||||
|
||||
CARGO_BUILD_TARGET = "${hostSystem.cpu.name}-pc-windows-gnu";
|
||||
|
||||
# fixes issues related to libring
|
||||
TARGET_CC = "${pkgs.pkgsCross.mingwW64.stdenv.cc}/bin/${pkgs.pkgsCross.mingwW64.stdenv.cc.targetPrefix}cc";
|
||||
|
||||
#fixes issues related to openssl
|
||||
OPENSSL_DIR = "${pkgs.openssl.dev}";
|
||||
OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib";
|
||||
OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include/";
|
||||
|
||||
depsBuildBuild = [
|
||||
pkgs.pkgsCross.mingwW64.stdenv.cc
|
||||
pkgs.pkgsCross.mingwW64.windows.pthreads
|
||||
];
|
||||
})
|
||||
|
||||
|
||||
# add the attrs passed to mkMizeRustModule
|
||||
// attrs
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
||||
buildModule = path: extraArgs: pkgs.callPackage ((import path).module or (attrs: null)) ({
|
||||
inherit buildMizeForSystem mizeBuildConfig mizeBuildConfigStr;
|
||||
inherit mkSelString craneLib toolchain_version;
|
||||
inherit mkMizeModule mkMizeRustModule buildModule findModules hostSystem pkgsCross pkgsNative mkMizeRustShell;
|
||||
inherit fenix;
|
||||
mize_version = "0.0.1";
|
||||
} // extraArgs );
|
||||
|
||||
buildLib = path: extraArgs: pkgs.callPackage ((import path).lib or (args: {})) {
|
||||
inherit mkSelString craneLib toolchain_version;
|
||||
inherit mkMizeModule mkMizeRustModule buildModule findModules hostSystem pkgsCross pkgsNative mkMizeRustShell;
|
||||
mize_version = "0.0.1";
|
||||
};
|
||||
|
||||
|
||||
mizeBuildConfig = {
|
||||
namespace = "mize.buildtime.ns";
|
||||
module_url = "c2vi.dev";
|
||||
selector = builtins.fromJSON (mkSelString {});
|
||||
};
|
||||
|
||||
mizeBuildConfigStr = let
|
||||
settingsFormat = pkgs.formats.toml { };
|
||||
in settingsFormat.generate "mize-build-config.toml" {
|
||||
config = mizeBuildConfig;
|
||||
};
|
||||
|
||||
|
||||
################# dev Shels ###################
|
||||
|
||||
mkMizeRustShell = attrs: mkMizeModuleShell (attrs // {
|
||||
shellHook = ''
|
||||
mize_project_root=$(${lib.getExe pkgs.git} rev-parse --show-toplevel)
|
||||
export MIZE_CONFIG_FILES=$mize_project_root/mize_config.toml
|
||||
'';
|
||||
#_shell_type = "rust";
|
||||
nativeBuildInputs = attrs.nativeBuildInputs or [] ++ [
|
||||
pkgs.wasm-pack
|
||||
pkgs.dioxus-cli
|
||||
pkgs.spacetimedb
|
||||
pkgs.deno
|
||||
pkgs.pkg-config
|
||||
pkgs.gobject-introspection
|
||||
pkgs.gobject-introspection.dev
|
||||
|
||||
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
|
||||
(pkgs.buildWasmBindgenCli rec {
|
||||
src = pkgs.fetchCrate {
|
||||
pname = "wasm-bindgen-cli";
|
||||
version = "0.2.114";
|
||||
hash = "sha256-xrCym+rFY6EUQFWyWl6OPA+LtftpUAE5pIaElAIVqW0=";
|
||||
};
|
||||
|
||||
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
|
||||
inherit src;
|
||||
inherit (src) pname version;
|
||||
hash = "sha256-Z8+dUXPQq7S+Q7DWNr2Y9d8GMuEdSnq00quUR0wDNPM=";
|
||||
};
|
||||
})
|
||||
(fenix.packages."x86_64-linux".combine [
|
||||
fenix.packages."x86_64-linux".latest.toolchain
|
||||
fenix.packages."x86_64-linux".targets.wasm32-unknown-unknown.latest.toolchain
|
||||
])
|
||||
|
||||
# the shell script, to mk the dist folder, for a standard rust module
|
||||
(pkgs.writeShellApplication {
|
||||
name = "mkdist";
|
||||
# should we use the folder, that cargo locate-project gives us??? or just cp ./target/debug/
|
||||
text = ''
|
||||
mkdir -p ./dist
|
||||
mkdir -p ./dist/lib
|
||||
|
||||
cp ./target/debug/libmize_module*.so ./dist/lib
|
||||
cp ./target/debug/libmize_module*.a ./dist/lib || true
|
||||
|
||||
'';
|
||||
})
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
pango
|
||||
webkitgtk_4_1
|
||||
openssl
|
||||
wasm-bindgen-cli
|
||||
lld_20
|
||||
xdotool
|
||||
];
|
||||
|
||||
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
|
||||
xdotool
|
||||
webkitgtk_4_1
|
||||
pango
|
||||
openssl
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
];
|
||||
|
||||
|
||||
});
|
||||
|
||||
mkMizeModuleShell = attrs: pkgs.mkShell (attrs // {
|
||||
MIZE_BUILD_CONFIG = mizeBuildConfigStr;
|
||||
IS_MIZE_DEV_SHELL = "true";
|
||||
});
|
||||
|
||||
|
||||
################# output attrset ###################
|
||||
|
||||
in rec {
|
||||
|
||||
main = modules.mize;
|
||||
|
||||
system = hostSystem;
|
||||
|
||||
|
||||
modules = builtins.listToAttrs ( map ( mod: { name = mod.modName; value = mod; } ) modulesList );
|
||||
|
||||
# for debugging
|
||||
|
||||
modulesLibList = map (mod: buildLib mod {}) modulesFileList;
|
||||
modulesLib = pkgs.lib.lists.foldr (a: b: b // a) {} modulesLibList;
|
||||
modulesList = pkgs.lib.lists.remove null (map (mod: (buildModule mod modulesLib)) modulesFileList);
|
||||
|
||||
inherit toolchain_version_drv pkgsCross craneLib;
|
||||
|
||||
inherit mize_module_path dirs_from_path dirs_in_nix_store from_env_var;
|
||||
|
||||
};
|
||||
|
||||
|
||||
mkInstallPhase = mize: ''
|
||||
# install mize
|
||||
mkdir -p $out/mize/${mize.system.name}
|
||||
|
||||
cp -r ${mize.main}/* $out/mize/${mize.system.name}/
|
||||
|
||||
# intall the modules
|
||||
${mkModulesInstallPhase mize.modulesList}
|
||||
'';
|
||||
|
||||
mkModulesInstallPhase = modules: pkgs.lib.concatStringsSep "\n" (map mkModuleInstallPhase modules);
|
||||
|
||||
mkModuleInstallPhase = module: let
|
||||
hash = module.hash;
|
||||
in ''
|
||||
echo got module: ${module.name}
|
||||
echo out: $out
|
||||
echo hash: ${hash}
|
||||
cp --no-preserve=mode,ownership -r ${module}/* $out/mize/dist/${hash}-${module.modName}
|
||||
${pkgs.gnutar}/bin/tar -czf $out/mize/dist/${hash}-${module.modName}.tar.gz -C ${module} .
|
||||
echo '${module.selector_string}' > $out/mize/dist/${hash}-${module.modName}/selector
|
||||
'';
|
||||
|
||||
dist = systems: stdenv.mkDerivation {
|
||||
name = "mize-dist";
|
||||
dontUnpack = true;
|
||||
|
||||
# so that those binaries run on average linux-gnu systems
|
||||
dontPatchShebangs = true;
|
||||
|
||||
buildPhase = ''
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
mkdir -p $out/mize
|
||||
mkdir -p $out/mize/dist
|
||||
|
||||
'' ;# + pkgs.lib.concatStringsSep "\n" (map mkInstallPhase (map buildMizeForSystem systems));
|
||||
|
||||
|
||||
nativeBuildInputs = [
|
||||
];
|
||||
};
|
||||
|
||||
moduleShells = system: let
|
||||
mizeFor = buildMizeForSystem system;
|
||||
in builtins.listToAttrs ( map ( mod: { name = mod.modName; value = (mod.devShell); } ) mizeFor.modulesList );
|
||||
|
||||
random = systems: rec {
|
||||
|
||||
distInstallPhase = map mkInstallPhase (map buildMizeForSystem systems);
|
||||
mizeList = map buildMizeForSystem systems;
|
||||
mize-x86_64-linux = buildMizeForSystem "x86_64-linux";
|
||||
mize-x86_64-linux-installPhase = mkInstallPhase mize-x86_64-linux;
|
||||
modulesListDrv = module_list_drv mize_modules;
|
||||
inherit modulesFileList;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -9,3 +9,5 @@ proc-macro = true
|
||||
[dependencies]
|
||||
quote = "1.0.44"
|
||||
syn = "2.0.114"
|
||||
proc-macro2 = "1.0"
|
||||
ciborium = "0.2"
|
||||
554
macros/src/lib.rs
Normal file
554
macros/src/lib.rs
Normal file
@@ -0,0 +1,554 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{self, parse::Parse, parse::ParseStream, Ident, LitStr, Token, Type, TypePath};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token;
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////// include_native! macro ///////////////////////////////////////
|
||||
/// A procedural macro to register methods and associated functions of a foreign type with mize.
|
||||
///
|
||||
/// Usage 1: Simplified syntax with no-argument methods
|
||||
/// ```ignore
|
||||
/// include_native!("Command", clap::Command, [get_name, is_empty]);
|
||||
/// ```
|
||||
///
|
||||
/// Usage 2: With argument specifications
|
||||
/// ```ignore
|
||||
/// include_native!("Command", clap::Command, [
|
||||
/// arg(arg: clap::Arg),
|
||||
/// subcommand(cmd: clap::Command)
|
||||
/// ]);
|
||||
/// ```
|
||||
///
|
||||
/// Usage 3: Block syntax separating associated functions and methods
|
||||
/// ```ignore
|
||||
/// include_native!("Command", clap::Command, {
|
||||
/// associated: [new(name: String)],
|
||||
/// methods: [arg(arg: clap::Arg), subcommand(cmd: clap::Command)]
|
||||
/// });
|
||||
/// ```
|
||||
|
||||
/// A function specification: name or name(arg1: Type1, arg2: Type2, ...)
|
||||
#[derive(Debug, Clone)]
|
||||
struct FuncSpec {
|
||||
name: Ident,
|
||||
args: Vec<(Ident, Type)>,
|
||||
}
|
||||
|
||||
impl Parse for FuncSpec {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name: Ident = input.parse()?;
|
||||
let mut args = Vec::new();
|
||||
|
||||
// Check for parentheses with arguments
|
||||
if input.peek(syn::token::Paren) {
|
||||
let content;
|
||||
syn::parenthesized!(content in input);
|
||||
|
||||
// Parse comma-separated arg: Type pairs
|
||||
while !content.is_empty() {
|
||||
let arg_name: Ident = content.parse()?;
|
||||
content.parse::<Token![:]>()?;
|
||||
let arg_type: Type = content.parse()?;
|
||||
args.push((arg_name, arg_type));
|
||||
|
||||
if !content.is_empty() {
|
||||
content.parse::<Token![,]>()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FuncSpec { name, args })
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to parse a list of function specifications
|
||||
struct FuncSpecList(Vec<FuncSpec>);
|
||||
|
||||
impl Parse for FuncSpecList {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let punctuated = Punctuated::<FuncSpec, Token![,]>::parse_terminated(input)?;
|
||||
Ok(FuncSpecList(punctuated.into_iter().collect()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for the simplified syntax: include_native!("name", Type, [method1, method2, ...])
|
||||
struct IncludeNativeSimple {
|
||||
name: LitStr,
|
||||
type_path: TypePath,
|
||||
methods: Vec<FuncSpec>,
|
||||
}
|
||||
|
||||
impl Parse for IncludeNativeSimple {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
let type_path = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
let methods = content.parse::<FuncSpecList>()?.0;
|
||||
|
||||
Ok(IncludeNativeSimple { name, type_path, methods })
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct for parsing the block syntax with associated and methods
|
||||
struct IncludeNativeBlock {
|
||||
name: LitStr,
|
||||
type_path: TypePath,
|
||||
associated: Vec<FuncSpec>,
|
||||
methods: Vec<FuncSpec>,
|
||||
}
|
||||
|
||||
impl Parse for IncludeNativeBlock {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
let type_path = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
let content;
|
||||
syn::braced!(content in input);
|
||||
|
||||
let mut associated = Vec::new();
|
||||
let mut methods = Vec::new();
|
||||
|
||||
// Parse the content looking for associated: [...] and methods: [...]
|
||||
// We'll collect all tokens first and then parse
|
||||
let tokens: Vec<proc_macro2::TokenTree> = content.parse::<proc_macro2::TokenStream>()?.into_iter().collect();
|
||||
let mut i = 0;
|
||||
while i < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Ident(id) = &tokens[i] {
|
||||
let id_str = id.to_string();
|
||||
if id_str == "associated" {
|
||||
// Find colon
|
||||
let mut j = i + 1;
|
||||
while j < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Punct(p) = &tokens[j] {
|
||||
if p.as_char() == ':' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
j += 1; // Skip colon
|
||||
|
||||
// Find bracket group
|
||||
while j < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Group(g) = &tokens[j] {
|
||||
if g.delimiter() == proc_macro2::Delimiter::Bracket {
|
||||
associated = syn::parse2::<FuncSpecList>(g.stream())
|
||||
.map(|l| l.0)
|
||||
.unwrap_or_default();
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
i = j + 1;
|
||||
} else if id_str == "methods" {
|
||||
// Find colon
|
||||
let mut j = i + 1;
|
||||
while j < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Punct(p) = &tokens[j] {
|
||||
if p.as_char() == ':' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
j += 1; // Skip colon
|
||||
|
||||
// Find bracket group
|
||||
while j < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Group(g) = &tokens[j] {
|
||||
if g.delimiter() == proc_macro2::Delimiter::Bracket {
|
||||
methods = syn::parse2::<FuncSpecList>(g.stream())
|
||||
.map(|l| l.0)
|
||||
.unwrap_or_default();
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
i = j + 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IncludeNativeBlock { name, type_path, associated, methods })
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_native(input: TokenStream) -> TokenStream {
|
||||
// Try to parse as simplified syntax: include_native!("name", Type, [m1, m2, ...])
|
||||
if let Ok(simple) = syn::parse::<IncludeNativeSimple>(input.clone()) {
|
||||
return expand_simple_include_native(simple);
|
||||
}
|
||||
|
||||
// Try to parse as block syntax: include_native!("name", Type, { associated: [...], methods: [...] })
|
||||
if let Ok(block) = syn::parse::<IncludeNativeBlock>(input) {
|
||||
return expand_block_include_native(block);
|
||||
}
|
||||
|
||||
// If both fail, return a compile error
|
||||
syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"Failed to parse include_native! input. Expected:\n \
|
||||
include_native!(\"name\", Type, [method1, method2(arg: Type), ...])\n or\n \
|
||||
include_native!(\"name\", Type, { associated: [func1(arg: Type)], methods: [method1] })",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Generate the argument extraction code for a function spec
|
||||
fn generate_arg_extraction(args: &[(Ident, Type)]) -> Vec<proc_macro2::TokenStream> {
|
||||
args.iter().enumerate().map(|(i, (arg_name, arg_type))| {
|
||||
quote! {
|
||||
let #arg_name = args.get_path(#i)?.native_clone::<#arg_type>()?;
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// Generate the argument passing code for a function call
|
||||
fn generate_arg_passing(args: &[(Ident, Type)]) -> Vec<proc_macro2::TokenStream> {
|
||||
args.iter().map(|(arg_name, _)| {
|
||||
quote! { #arg_name }
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// Last path segment of a Type, for signature strings (e.g. `clap::Command` -> `Command`).
|
||||
fn type_to_sig_name(ty: &Type) -> String {
|
||||
match ty {
|
||||
Type::Path(p) => p
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.map(|s| s.ident.to_string())
|
||||
.unwrap_or_else(|| quote!(#ty).to_string()),
|
||||
_ => quote!(#ty).to_string().replace(' ', ""),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a signature string like `"String, Command"` from FuncSpec args.
|
||||
fn synthesize_signature(args: &[(Ident, Type)]) -> String {
|
||||
args.iter()
|
||||
.map(|(_, ty)| type_to_sig_name(ty))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
fn expand_simple_include_native(simple: IncludeNativeSimple) -> TokenStream {
|
||||
let name = simple.name;
|
||||
let type_path = simple.type_path;
|
||||
let methods = simple.methods;
|
||||
|
||||
let type_name_str = quote!(#type_path).to_string().replace(' ', "");
|
||||
let method_names: Vec<String> = methods.iter().map(|m| m.name.to_string()).collect();
|
||||
let method_sigs: Vec<String> = methods.iter().map(|m| synthesize_signature(&m.args)).collect();
|
||||
|
||||
let method_calls = methods.iter().map(|method| {
|
||||
let method_name = method.name.to_string();
|
||||
let method_ident = &method.name;
|
||||
let signature = synthesize_signature(&method.args);
|
||||
let arg_extractions = generate_arg_extraction(&method.args);
|
||||
let arg_passings = generate_arg_passing(&method.args);
|
||||
|
||||
quote! {
|
||||
.method(#method_name, #signature, |args| {
|
||||
#(#arg_extractions)*
|
||||
let this = mize::prelude::get_global_mize()?
|
||||
.get_config(#name)?
|
||||
.native_mut::<#type_path>()?;
|
||||
let result = this.#method_ident(#(#arg_passings),*);
|
||||
Ok(mize::item::ItemData::from_native(result)?)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
{
|
||||
if let Ok(mize) = mize::prelude::get_global_mize() {
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
let _ = mize.record_native(
|
||||
#name,
|
||||
#type_name_str,
|
||||
Vec::new(),
|
||||
vec![#(#method_names.to_string()),*],
|
||||
Vec::new(),
|
||||
vec![#(#method_sigs.to_string()),*],
|
||||
);
|
||||
}
|
||||
}
|
||||
mize::config::opt(#name)
|
||||
#(#method_calls)*
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn expand_block_include_native(block: IncludeNativeBlock) -> TokenStream {
|
||||
let name = block.name;
|
||||
let type_path = block.type_path;
|
||||
let associated = block.associated;
|
||||
let methods = block.methods;
|
||||
|
||||
let type_name_str = quote!(#type_path).to_string().replace(' ', "");
|
||||
let associated_names: Vec<String> = associated.iter().map(|m| m.name.to_string()).collect();
|
||||
let method_names: Vec<String> = methods.iter().map(|m| m.name.to_string()).collect();
|
||||
let associated_sigs: Vec<String> = associated.iter().map(|m| synthesize_signature(&m.args)).collect();
|
||||
let method_sigs: Vec<String> = methods.iter().map(|m| synthesize_signature(&m.args)).collect();
|
||||
|
||||
let method_calls = methods.iter().map(|method| {
|
||||
let method_name = method.name.to_string();
|
||||
let method_ident = &method.name;
|
||||
let signature = synthesize_signature(&method.args);
|
||||
let arg_extractions = generate_arg_extraction(&method.args);
|
||||
let arg_passings = generate_arg_passing(&method.args);
|
||||
|
||||
quote! {
|
||||
.method(#method_name, #signature, |args| {
|
||||
#(#arg_extractions)*
|
||||
let this = mize::prelude::get_global_mize()?
|
||||
.get_config(#name)?
|
||||
.native_mut::<#type_path>()?;
|
||||
let result = this.#method_ident(#(#arg_passings),*);
|
||||
Ok(mize::item::ItemData::from_native(result)?)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let associated_calls = associated.iter().map(|func| {
|
||||
let func_name = func.name.to_string();
|
||||
let func_ident = &func.name;
|
||||
let signature = synthesize_signature(&func.args);
|
||||
let arg_extractions = generate_arg_extraction(&func.args);
|
||||
let arg_passings = generate_arg_passing(&func.args);
|
||||
|
||||
quote! {
|
||||
.method(#func_name, #signature, |args| {
|
||||
#(#arg_extractions)*
|
||||
let result = <#type_path>::#func_ident(#(#arg_passings),*);
|
||||
Ok(mize::item::ItemData::from_native(result)?)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
{
|
||||
if let Ok(mize) = mize::prelude::get_global_mize() {
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
let _ = mize.record_native(
|
||||
#name,
|
||||
#type_name_str,
|
||||
vec![#(#associated_names.to_string()),*],
|
||||
vec![#(#method_names.to_string()),*],
|
||||
vec![#(#associated_sigs.to_string()),*],
|
||||
vec![#(#method_sigs.to_string()),*],
|
||||
);
|
||||
}
|
||||
}
|
||||
mize::config::opt(#name)
|
||||
#(#associated_calls)*
|
||||
#(#method_calls)*
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////// data! macro ///////////////////////////////////////
|
||||
// A procedural macro to define data in Nix-style and expand to a `ciborium::Value` (CBOR).
|
||||
//
|
||||
// Add this to your `Cargo.toml`:
|
||||
// ```toml
|
||||
// [lib]
|
||||
// proc-macro = true
|
||||
//
|
||||
// [dependencies]
|
||||
// syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
// quote = "1.0"
|
||||
// proc-macro2 = "1.0"
|
||||
// ciborium = "0.2"
|
||||
// ```
|
||||
|
||||
use syn::Lit;
|
||||
|
||||
// --- AST ---
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NixExpr {
|
||||
Map(Vec<(String, NixExpr)>),
|
||||
List(Vec<NixExpr>),
|
||||
Literal(Literal),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Literal {
|
||||
String(String),
|
||||
Int(i128),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
#[allow(unused)]
|
||||
Null,
|
||||
}
|
||||
|
||||
// --- Code Generation ---
|
||||
|
||||
impl NixExpr {
|
||||
fn to_cbor_tokens(&self) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
NixExpr::Map(entries) => {
|
||||
let entry_tokens: Vec<_> = entries
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let key = syn::LitStr::new(k, proc_macro2::Span::call_site());
|
||||
let value_tokens = v.to_cbor_tokens();
|
||||
quote! {
|
||||
(mize::ciborium::Value::Text(#key.to_string()), #value_tokens)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
quote! {
|
||||
mize::ciborium::Value::Map({
|
||||
let mut m = Vec::<(mize::ciborium::Value, mize::ciborium::Value)>::new();
|
||||
#(m.push(#entry_tokens);)*
|
||||
m
|
||||
})
|
||||
}
|
||||
}
|
||||
NixExpr::List(items) => {
|
||||
let item_tokens: Vec<_> = items.iter().map(|item| item.to_cbor_tokens()).collect();
|
||||
quote! {
|
||||
mize::ciborium::Value::Array({
|
||||
let mut v = Vec::<mize::ciborium::Value>::new();
|
||||
#(v.push(#item_tokens);)*
|
||||
v
|
||||
})
|
||||
}
|
||||
}
|
||||
NixExpr::Literal(lit) => lit.to_cbor_tokens(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Literal {
|
||||
fn to_cbor_tokens(&self) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
Literal::String(s) => {
|
||||
let s = syn::LitStr::new(s, proc_macro2::Span::call_site());
|
||||
quote! { mize::ciborium::Value::Text(#s.to_string()) }
|
||||
}
|
||||
Literal::Int(i) => quote! { mize::ciborium::Value::Integer(#i) },
|
||||
Literal::Float(f) => quote! { mize::ciborium::Value::Float(#f) },
|
||||
Literal::Bool(b) => quote! { mize::ciborium::Value::Bool(#b) },
|
||||
Literal::Null => quote! { mize::ciborium::Value::Null },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Parsing ---
|
||||
|
||||
struct NixKeyValue {
|
||||
key: Ident,
|
||||
_eq: Token![=],
|
||||
value: NixExpr,
|
||||
}
|
||||
|
||||
impl Parse for NixKeyValue {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(NixKeyValue {
|
||||
key: input.parse()?,
|
||||
_eq: input.parse()?,
|
||||
value: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for NixExpr {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
// Parse a map: { a = 1; b = 2; }
|
||||
if input.peek(token::Brace) {
|
||||
let content;
|
||||
syn::braced!(content in input);
|
||||
let pairs = Punctuated::<NixKeyValue, Token![;]>::parse_terminated(&content)?;
|
||||
let entries = pairs
|
||||
.into_iter()
|
||||
.map(|kv| (kv.key.to_string(), kv.value))
|
||||
.collect();
|
||||
return Ok(NixExpr::Map(entries));
|
||||
}
|
||||
|
||||
// Parse a list: [ 1, 2, 3 ]
|
||||
if input.peek(token::Bracket) {
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
let items = Punctuated::<NixExpr, Token![,]>::parse_terminated(&content)?;
|
||||
return Ok(NixExpr::List(items.into_iter().collect()));
|
||||
}
|
||||
|
||||
// Parse literals
|
||||
if input.peek(Lit) {
|
||||
let lit = input.parse::<Lit>()?;
|
||||
return match lit {
|
||||
Lit::Str(s) => Ok(NixExpr::Literal(Literal::String(s.value()))),
|
||||
Lit::Int(i) => Ok(NixExpr::Literal(Literal::Int(i.base10_parse::<i128>()?))),
|
||||
Lit::Float(f) => Ok(NixExpr::Literal(Literal::Float(f.base10_parse::<f64>()?))),
|
||||
Lit::Bool(b) => Ok(NixExpr::Literal(Literal::Bool(b.value))),
|
||||
_ => Err(input.error("Unsupported literal")),
|
||||
};
|
||||
}
|
||||
|
||||
Err(input.error("Expected a Nix expression (map, list, or literal)"))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Top-level AttrSet Parsing ---
|
||||
|
||||
struct NixAttrSet(Vec<NixKeyValue>);
|
||||
|
||||
impl syn::parse::Parse for NixAttrSet {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let pairs = Punctuated::<NixKeyValue, Token![;]>::parse_terminated(input)?;
|
||||
Ok(NixAttrSet(pairs.into_iter().collect()))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Macro Entry ---
|
||||
|
||||
#[proc_macro]
|
||||
pub fn data(input: TokenStream) -> TokenStream {
|
||||
// Try to parse top-level attrset (without outer braces)
|
||||
let expr = syn::parse::<NixAttrSet>(input.clone())
|
||||
.map(|attrset| {
|
||||
NixExpr::Map(
|
||||
attrset.0
|
||||
.into_iter()
|
||||
.map(|kv| (kv.key.to_string(), kv.value))
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.or_else(|_| {
|
||||
// fallback: parse legacy behavior (braced map, list, or literal)
|
||||
syn::parse::<NixExpr>(input)
|
||||
});
|
||||
|
||||
let tokens = match expr {
|
||||
Ok(expr) => expr.to_cbor_tokens(),
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
TokenStream::from(tokens)
|
||||
}
|
||||
56
md
56
md
@@ -1,56 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
MIZE_SRC="/home/me/work/mize" # hardcoded for now
|
||||
RSYNC_EXCLUDES="--exclude=.git --exclude=node_modules --exclude=target --exclude=mize_config.toml --exclude=rust_dist --exclude=deno_dist --exclude=dist --exclude=result --exclude=pkg"
|
||||
|
||||
function main {
|
||||
if [[ "$1" == "dlu" ]]; then
|
||||
deploy_lush
|
||||
fi
|
||||
|
||||
if [[ "$1" == "dma" ]]; then
|
||||
deploy_main_website
|
||||
fi
|
||||
|
||||
if [[ "$1" == "dmo" ]]; then
|
||||
deploy_main_obsidian
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
function deploy_lush {
|
||||
echo "c2vi: Deploying to lush..."
|
||||
|
||||
# Copy files to lush
|
||||
rsync -r -u -v --delete --mkpath $RSYNC_EXCLUDES $MIZE_SRC/* lush:here/mize
|
||||
|
||||
# compile
|
||||
ssh lush nix develop /home/me/here/mize --impure -v -L -c sh -c "cargo build --release --manifest-path /home/me/here/mize/packages/ppc/Cargo.toml"
|
||||
|
||||
# restart service
|
||||
ssh lush "sudo systemctl restart ppc"
|
||||
}
|
||||
|
||||
function deploy_main_website {
|
||||
echo "c2vi: Deploying website to fes"
|
||||
|
||||
# compile
|
||||
cargo build --release --no-default-features --features server --manifest-path $MIZE_SRC/packages/ppc/Cargo.toml
|
||||
|
||||
# upload
|
||||
scp $MIZE_SRC/target/release/ppc fes:here
|
||||
|
||||
# restart service
|
||||
ssh fe "sudo systemctl restart ppc"
|
||||
}
|
||||
|
||||
function deploy_main_obsidian {
|
||||
echo "c2vi: Deploying to local main obsidian vault..."
|
||||
|
||||
deno run --config $MIZE_SRC/packages/ppc/platform/obsidian/deno.json build
|
||||
|
||||
cp -r $MIZE_SRC/packages/ppc/platform/obsidian/dist/* ~/work/things/.obsidian/plugins/ppc/
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,85 +0,0 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn mize_part(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as DeriveInput);
|
||||
let struct_name = &input.ident;
|
||||
|
||||
// Parse name from attribute
|
||||
let part_name = if !attr.is_empty() {
|
||||
let lit: syn::LitStr = parse_macro_input!(attr);
|
||||
lit.value()
|
||||
} else {
|
||||
struct_name.to_string().to_lowercase()
|
||||
};
|
||||
|
||||
// Verify mize field exists
|
||||
let has_mize = match &input.data {
|
||||
Data::Struct(s) => match &s.fields {
|
||||
Fields::Named(f) => f
|
||||
.named
|
||||
.iter()
|
||||
.any(|field| field.ident.as_ref().map(|i| i == "mize").unwrap_or(false)),
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !has_mize {
|
||||
return syn::Error::new_spanned(struct_name, "Struct must have a 'mize: Mize' field")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#input
|
||||
|
||||
// impl the default new impl
|
||||
impl mize::MizePartGenerated for #struct_name {
|
||||
fn name_generated(&self) -> &'static str {
|
||||
#part_name
|
||||
}
|
||||
fn get_mize_generated(&mut self) -> &mut Mize {
|
||||
&mut self.mize
|
||||
}
|
||||
fn as_any_generated(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut_generated(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn into_any_generated(self: Box<Self>) -> Box<dyn std::any::Any> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl mize::MizePartCreateGenerated for #struct_name {
|
||||
fn create_generated(mize: Mize) -> Self {
|
||||
Self {
|
||||
mize,
|
||||
.. Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl std::ops::Deref for mize::MizePartGuard<#struct_name> {
|
||||
type Target = #struct_name;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.part.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for mize::MizePartGuard<#struct_name> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut *self.part.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
[package]
|
||||
name = "marts"
|
||||
description = "marts = mize_parts - parts collection of the Mize framework"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.59", optional = true, features = ["string"] }
|
||||
flume = "0.12.0"
|
||||
futures = "0.3.32"
|
||||
mize = { path = "../mize/", default-features = false }
|
||||
tokio = "1.49.0"
|
||||
# https://github.com/rscarson/rustyscript/pull/415
|
||||
rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
|
||||
#deno_core = { version = "0.380.1", optional = true }
|
||||
chrono = "0.4.44"
|
||||
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.149"
|
||||
rusqlite = { version = "0.31", features = ["bundled"], optional = true }
|
||||
dioxus = { version = "0.7.1", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["target-os", "desktop", "server"]
|
||||
target-os = [ "clap", "mize/target-os", "rustyscript", "rusqlite", "dioxus" ]
|
||||
desktop = ["dioxus/desktop"]
|
||||
server = []
|
||||
target-js = [ "mize/target-js" ]
|
||||
clap = ["dep:clap"]
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
@@ -1,9 +0,0 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub fn main() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "my dashbaord" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,431 +0,0 @@
|
||||
import { createClient } from "@libsql/client";
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
||||
import { eq } from "drizzle-orm";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
export async function init(ppc: PPC) {
|
||||
ppc.c2vi = new C2vi(ppc);
|
||||
|
||||
ppc.cli
|
||||
.command("bed", "command to run when c2vi goes to bed")
|
||||
.action(async () => {
|
||||
await save_habitica_dailies(ppc);
|
||||
await save_habitica_logs(ppc);
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("clearTodos", "command to run when c2vi goes to bed")
|
||||
.alias("clt")
|
||||
.action(async () => {
|
||||
const todos = await ppc.habitica.get_tasks();
|
||||
for (const todo of todos) {
|
||||
await ppc.habitica.delete_task(todo.id);
|
||||
}
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("listTodos", "command to run when c2vi goes to bed")
|
||||
.alias("dut")
|
||||
.action(async () => {
|
||||
const todos = await ppc.habitica.get_tasks();
|
||||
for (const todo of todos) {
|
||||
console.log(todo.text);
|
||||
}
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("buyHealthPotion", "command to run when c2vi goes to bed")
|
||||
.alias("buyh")
|
||||
.action(async () => {
|
||||
await ppc.habitica.api_request("POST", "/user/buy-health-potion");
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("addActionItems", "command to run when c2vi goes to bed")
|
||||
.alias("aal")
|
||||
.action(async () => {
|
||||
await add_action_items(ppc);
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("test", "a command to run some test code")
|
||||
.alias("t")
|
||||
.action(async () => {});
|
||||
|
||||
ppc.cli
|
||||
.command("skip [num]", "command to skip a habitica todo")
|
||||
.alias("sk")
|
||||
.action((num = 1) => {
|
||||
skip_habitica_todo(ppc, parseInt(num));
|
||||
});
|
||||
|
||||
ppc.cli.command("dumpLog", "dump the complete habitica log").action(() => {
|
||||
dump_habitica_logs(ppc);
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("listDailies", "list all dailies")
|
||||
.action(async (folder, options) => {
|
||||
const allDailies = await ppc.c2vi.db.select().from(dailiesTable).all();
|
||||
for (const task of allDailies) {
|
||||
console.log(task.id + ": " + task.text);
|
||||
}
|
||||
});
|
||||
|
||||
ppc.cli
|
||||
.command("printTask <id>", "print a specific todo")
|
||||
.action(async (id) => {
|
||||
const data = await ppc.habitica.api_request("GET", `/tasks/${id}`, {});
|
||||
console.log(data.history);
|
||||
});
|
||||
}
|
||||
|
||||
class C2vi {
|
||||
[key: string]: any;
|
||||
|
||||
constructor(ppc: PPC) {
|
||||
this.ppc = ppc;
|
||||
this.db = drizzle(
|
||||
createClient({
|
||||
url: "file://" + ppc.config.local_storage_path + "/data.db",
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(d) {
|
||||
const pad = (n) => n.toString().padStart(2, "0");
|
||||
|
||||
// Custom YYYY-MM-DD format
|
||||
const formatted = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// ============= SCHEMA DEFINITION =============
|
||||
const dailiesTable = sqliteTable("habitica_dailies", {
|
||||
id: text("id").primaryKey(),
|
||||
text: text("text").notNull(),
|
||||
notes: text("notes"),
|
||||
priority: text("priority"), // 0.1, 1, 1.5, 2
|
||||
frequency: text("frequency"), // 'daily', 'weekly', etc.
|
||||
});
|
||||
const habiticaLogTable = sqliteTable("habitica_log", {
|
||||
date: text("date").primaryKey(),
|
||||
data: text("data").notNull(),
|
||||
});
|
||||
|
||||
async function save_habitica_dailies(ppc) {
|
||||
console.log("######### cli bed cmd #########");
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_dailies (id TEXT PRIMARY KEY, text TEXT NOT NULL, notes TEXT, priority TEXT, frequency TEXT)",
|
||||
);
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_log (date TEXT PRIMARY KEY, data TEXT NOT NULL)",
|
||||
);
|
||||
|
||||
// ============= DATABASE INITIALIZATION =============
|
||||
if (!ppc.config.habitica.user_id || !ppc.config.habitica.api_token) {
|
||||
console.error(
|
||||
"Error: HABITICA_USER_ID and HABITICA_API_TOKEN env vars required",
|
||||
);
|
||||
console.error(
|
||||
"Get these from Habitica Settings > API and configure them in the ppc.config",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const dailies = await ppc.habitica.get_tasks("dailys");
|
||||
|
||||
await writeDailiesToDatabase(dailies, ppc);
|
||||
}
|
||||
|
||||
async function dump_habitica_logs(ppc) {
|
||||
const entries = await ppc.c2vi.db.select().from(habiticaLogTable).all();
|
||||
for (const entry of entries) {
|
||||
console.log(entry.date + ": ", JSON.parse(entry.data));
|
||||
}
|
||||
}
|
||||
|
||||
async function skip_habitica_todo(ppc, num) {
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_dailies (id TEXT PRIMARY KEY, text TEXT NOT NULL, notes TEXT, priority TEXT, frequency TEXT)",
|
||||
);
|
||||
await ppc.c2vi.db.run(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_log (date TEXT PRIMARY KEY, data TEXT NOT NULL)",
|
||||
);
|
||||
|
||||
const todos = await ppc.habitica.get_tasks("todos");
|
||||
|
||||
const date = new Date();
|
||||
const save_for_yesterday = date.getHours() < 12;
|
||||
if (save_for_yesterday) {
|
||||
console.log(
|
||||
"Saving logs for yesterday!!!! because it is " +
|
||||
date.getHours() +
|
||||
"hours of the day",
|
||||
);
|
||||
date.setDate(date.getDate() - 1);
|
||||
}
|
||||
|
||||
const result = await ppc.c2vi.db
|
||||
.select()
|
||||
.from(habiticaLogTable)
|
||||
.where(eq(habiticaLogTable.date, formatDate(date)));
|
||||
|
||||
const data = result[0]
|
||||
? JSON.parse(result[0].data)
|
||||
: {
|
||||
dailies_done: [],
|
||||
dailies_skipped: [],
|
||||
todos_done: [],
|
||||
todos_skipped: [],
|
||||
};
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
const todo = todos[i];
|
||||
if (!todo) continue;
|
||||
await ppc.habitica.delete_task(todo.id);
|
||||
await incrementHabit(ppc);
|
||||
|
||||
data.todos_skipped.push({
|
||||
id: todo.id,
|
||||
text: todo.text,
|
||||
notes: todo.notes || "",
|
||||
checklist: todo.checklist || [],
|
||||
tags: todo.tags || [],
|
||||
});
|
||||
}
|
||||
|
||||
// saving to db
|
||||
await ppc.c2vi.db
|
||||
.insert(habiticaLogTable)
|
||||
.values({ date: formatDate(date), data: JSON.stringify(data) })
|
||||
.onConflictDoUpdate({
|
||||
target: habiticaLogTable.date,
|
||||
set: {
|
||||
data: JSON.stringify(data),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function save_habitica_logs(ppc) {
|
||||
const date = new Date();
|
||||
const save_for_yesterday = date.getHours() < 12;
|
||||
if (save_for_yesterday) {
|
||||
console.log(
|
||||
"Saving logs for yesterday!!!! because it is " +
|
||||
date.getHours() +
|
||||
"hours of the day",
|
||||
);
|
||||
date.setDate(date.getDate() - 1);
|
||||
}
|
||||
|
||||
const result = await ppc.c2vi.db
|
||||
.select()
|
||||
.from(habiticaLogTable)
|
||||
.where(eq(habiticaLogTable.date, formatDate(date)));
|
||||
|
||||
const data = result[0]
|
||||
? JSON.parse(result[0].data)
|
||||
: {
|
||||
dailies_done: [],
|
||||
dailies_skipped: [],
|
||||
todos_done: [],
|
||||
todos_skipped: [],
|
||||
};
|
||||
|
||||
// dailies
|
||||
const dailies = await ppc.habitica.get_tasks("dailys");
|
||||
for (const daily of dailies) {
|
||||
let completed = daily.completed;
|
||||
let isDue = daily.isDue;
|
||||
if (save_for_yesterday) {
|
||||
const yesterday = daily.history.reduce((prev, current) => {
|
||||
return prev.date > current.date ? prev : current;
|
||||
});
|
||||
completed = yesterday.completed;
|
||||
isDue = yesterday.isDue;
|
||||
}
|
||||
if (!isDue) {
|
||||
continue;
|
||||
}
|
||||
if (completed) {
|
||||
data.dailies_done.push(daily.id);
|
||||
console.log("daily-done: " + daily.text);
|
||||
} else {
|
||||
data.dailies_skipped.push(daily.id);
|
||||
console.log("daily-skipped: " + daily.text);
|
||||
}
|
||||
}
|
||||
|
||||
// todos done
|
||||
const todos = await ppc.habitica.get_tasks("completedTodos");
|
||||
for (const todo of todos) {
|
||||
const dateCompleted = new Date(todo.dateCompleted);
|
||||
|
||||
// check if this todo is already saved in yesterday's log entry
|
||||
if (save_for_yesterday) {
|
||||
// get yesterday log entry
|
||||
const result = await ppc.c2vi.db
|
||||
.select()
|
||||
.from(habiticaLogTable)
|
||||
.where(eq(habiticaLogTable.date, formatDate(date)));
|
||||
|
||||
const data = result[0]
|
||||
? JSON.parse(result[0].data)
|
||||
: {
|
||||
dailies_done: [],
|
||||
dailies_skipped: [],
|
||||
todos_done: [],
|
||||
todos_skipped: [],
|
||||
};
|
||||
|
||||
// check if this todo is already saved in yesterday's log entry
|
||||
const todoIndex = data.todos_done.findIndex((t) => t.id === todo.id);
|
||||
if (todoIndex !== -1) {
|
||||
continue; // skipp saving this todo
|
||||
}
|
||||
}
|
||||
|
||||
if (dateCompleted.toDateString() == date.toDateString()) {
|
||||
console.log("todo-done:", todo.text);
|
||||
data.todos_done.push({
|
||||
id: todo.id,
|
||||
text: todo.text,
|
||||
notes: todo.notes || "",
|
||||
checklist: todo.checklist || [],
|
||||
tags: todo.tags || [],
|
||||
});
|
||||
}
|
||||
|
||||
// we save_for_yesterday but the todo was completed today... also save that for this day
|
||||
if (
|
||||
save_for_yesterday &&
|
||||
dateCompleted.toDateString() == new Date().toDateString()
|
||||
) {
|
||||
console.log("todo-done:", todo.text);
|
||||
data.todos_done.push({
|
||||
id: todo.id,
|
||||
text: todo.text,
|
||||
notes: todo.notes || "",
|
||||
checklist: todo.checklist || [],
|
||||
tags: todo.tags || [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// saving to db
|
||||
await ppc.c2vi.db
|
||||
.insert(habiticaLogTable)
|
||||
.values({ date: formatDate(date), data: JSON.stringify(data) })
|
||||
.onConflictDoUpdate({
|
||||
target: habiticaLogTable.date,
|
||||
set: {
|
||||
data: JSON.stringify(data),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function writeDailiesToDatabase(dailies, ppc) {
|
||||
console.log(`Syncing ${dailies.length} dailies to database...`);
|
||||
|
||||
const records = dailies.map((daily) => ({
|
||||
id: daily.id,
|
||||
text: daily.text,
|
||||
notes: daily.notes || "",
|
||||
priority: daily.priority?.toString() || null,
|
||||
frequency: daily.frequency || "daily",
|
||||
}));
|
||||
|
||||
async function upsertMany(records) {
|
||||
for (const record of records) {
|
||||
await ppc.c2vi.db
|
||||
.insert(dailiesTable)
|
||||
.values(record)
|
||||
.onConflictDoUpdate({
|
||||
target: dailiesTable.id,
|
||||
set: {
|
||||
text: record.text,
|
||||
notes: record.notes,
|
||||
priority: record.priority,
|
||||
frequency: record.frequency,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await upsertMany(records);
|
||||
}
|
||||
|
||||
async function incrementHabit(ppc) {
|
||||
const habits = await ppc.habitica.get_tasks("habits");
|
||||
let id = "";
|
||||
for (const habit of habits) {
|
||||
if ((habit.text = "skipped a planned task")) {
|
||||
id = habit.id;
|
||||
}
|
||||
}
|
||||
await ppc.habitica.api_request("POST", `tasks/${id}/score/down`);
|
||||
}
|
||||
|
||||
async function add_action_items(ppc: PPC) {
|
||||
let task_ids = [];
|
||||
|
||||
const tmpFile = path.join(os.tmpdir(), `habitica-tasks-${Date.now()}.md`);
|
||||
const initialContent = `
|
||||
gu 10min TIMER
|
||||
uw 15min FROG
|
||||
b coffee/tee/kakau
|
||||
uw FROG
|
||||
b breakfast, brush teeth, lüften
|
||||
uw FROG
|
||||
|
||||
lunch
|
||||
|
||||
uw 15min TIMER of next days FROG
|
||||
fw
|
||||
dt 30min TIMER
|
||||
|
||||
fun 20:00 (smth that can be stopped easily)
|
||||
se 21:40
|
||||
read
|
||||
bed 22:10
|
||||
`;
|
||||
|
||||
fs.writeFileSync(tmpFile, initialContent.trim());
|
||||
|
||||
spawnSync("nvim", [tmpFile], { stdio: "inherit" });
|
||||
|
||||
// read file
|
||||
const listStr = fs.readFileSync(tmpFile, "utf8");
|
||||
|
||||
// delete file
|
||||
fs.unlinkSync(tmpFile);
|
||||
|
||||
// Simple parser: filter out empty lines
|
||||
const tasks = listStr
|
||||
.split("\n")
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length > 0);
|
||||
|
||||
// 5. Add tasks to Habitica
|
||||
for (const todoText of tasks) {
|
||||
const data = await ppc.habitica.api_request(
|
||||
"POST",
|
||||
"tasks/user",
|
||||
{},
|
||||
{ text: todoText, type: "todo" },
|
||||
);
|
||||
task_ids.push(data.id);
|
||||
console.log(`Added to-do: '${todoText}' with id '${data.id}'`);
|
||||
}
|
||||
|
||||
// 6. Move tasks to bottom
|
||||
for (const taskId of task_ids) {
|
||||
await ppc.habitica.api_request("POST", `tasks/${taskId}/move/to/-1`);
|
||||
}
|
||||
}
|
||||
@@ -1,607 +0,0 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use chrono::{Timelike, Utc};
|
||||
use clap::{Arg, Command};
|
||||
use rusqlite::{params, Connection};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizeResult};
|
||||
|
||||
use crate::cli::CliPart;
|
||||
use crate::habitica::Habitica;
|
||||
|
||||
mod dashboard;
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct C2vi {
|
||||
mize: Mize,
|
||||
db: Mutex<Option<Connection>>,
|
||||
}
|
||||
|
||||
impl MizePart for C2vi {
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("c2vi.local_storage_path");
|
||||
}
|
||||
}
|
||||
|
||||
impl C2vi {
|
||||
fn with_db<F, R>(&self, f: F) -> MizeResult<R>
|
||||
where
|
||||
F: FnOnce(&Connection) -> MizeResult<R>,
|
||||
{
|
||||
let guard = self
|
||||
.db
|
||||
.lock()
|
||||
.map_err(|e| mize_err!("DB lock poisoned: {}", e))?;
|
||||
let conn = guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| mize_err!("DB not initialized"))?;
|
||||
f(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn c2vi(mize: &mut Mize) -> MizeResult<()> {
|
||||
#[cfg(feature = "client")]
|
||||
c2vi_client(mize)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn c2vi_client(mize: &mut Mize) -> MizeResult<()> {
|
||||
//let storage_path = mize.get_config("c2vi.local_storage_path")?.as_
|
||||
let storage_path = "/home/me/work/app-data/ppc";
|
||||
println!("storage_path: {}", storage_path);
|
||||
let db_path = std::path::PathBuf::from(&storage_path).join("data.db");
|
||||
let conn = Connection::open(&db_path)
|
||||
.map_err(|e| mize_err!("Failed to open C2vi database at {:?}: {}", db_path, e))?;
|
||||
|
||||
setup_tables(&conn)?;
|
||||
|
||||
let c2vi_part = C2vi {
|
||||
mize: mize.clone(),
|
||||
db: Mutex::new(Some(conn)),
|
||||
};
|
||||
mize.register_part(Box::new(c2vi_part))?;
|
||||
|
||||
// Register CLI subcommands
|
||||
let mut cli = mize.get_part_native::<CliPart>("cli")?;
|
||||
register_subcommands(&mut cli);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_tables(conn: &Connection) -> MizeResult<()> {
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_dailies (
|
||||
id TEXT PRIMARY KEY,
|
||||
text TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
priority TEXT,
|
||||
frequency TEXT
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error creating habitica_dailies: {}", e))?;
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS habitica_log (
|
||||
date TEXT PRIMARY KEY,
|
||||
data TEXT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error creating habitica_log: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_subcommands(cli: &mut CliPart) {
|
||||
cli.subcommand(
|
||||
Command::new("c2vi-dash").about("Run the c2vi dashboard gui"),
|
||||
|_matches, mut mize| Ok(dioxus::launch(dashboard::main)),
|
||||
);
|
||||
cli.subcommand(
|
||||
Command::new("bed").about("Command to run when c2vi goes to bed"),
|
||||
|_matches, mut mize| {
|
||||
save_habitica_dailies(&mut mize)?;
|
||||
save_habitica_logs(&mut mize)?;
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("clearTodos")
|
||||
.about("Clear all Habitica todos")
|
||||
.alias("clt"),
|
||||
|_matches, mut mize| {
|
||||
let todos = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.get_tasks("todos")?
|
||||
};
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
if let Some(arr) = todos.as_array() {
|
||||
for todo in arr {
|
||||
if let Some(id) = todo["id"].as_str() {
|
||||
hab.delete_task(id)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("listTodos")
|
||||
.about("List all Habitica todos")
|
||||
.alias("dut"),
|
||||
|_matches, mut mize| {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let todos = hab.get_tasks("todos")?;
|
||||
if let Some(arr) = todos.as_array() {
|
||||
for todo in arr {
|
||||
if let Some(text) = todo["text"].as_str() {
|
||||
println!("{}", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("buyHealthPotion")
|
||||
.about("Buy a health potion")
|
||||
.alias("buyh"),
|
||||
|_matches, mut mize| {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
"user/buy-health-potion".to_string(),
|
||||
json!({}),
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("addActionItems")
|
||||
.about("Add action items from temp file")
|
||||
.alias("aal"),
|
||||
|_matches, mut mize| add_action_items(&mut mize),
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("skip")
|
||||
.about("Skip a Habitica todo")
|
||||
.alias("sk")
|
||||
.arg(Arg::new("num").default_value("1")),
|
||||
|matches, mut mize| {
|
||||
let num: usize = matches
|
||||
.get_one::<String>("num")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap_or(1);
|
||||
skip_habitica_todo(&mut mize, num)
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("dumpLog").about("Dump the complete Habitica log"),
|
||||
|_matches, mut mize| dump_habitica_logs(&mut mize),
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("listDailies").about("List all dailies from local DB"),
|
||||
|_matches, mut mize| {
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT id, text FROM habitica_dailies")
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
||||
})
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
for row in rows {
|
||||
let (id, text) = row.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
println!("{}: {}", id, text);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("printTask")
|
||||
.about("Print a specific task from Habitica")
|
||||
.arg(Arg::new("id").required(true)),
|
||||
|matches, mut mize| {
|
||||
let id = matches.get_one::<String>("id").unwrap();
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let data = hab.api_request(reqwest::Method::GET, format!("tasks/{}", id), json!({}))?;
|
||||
if let Some(history) = data.get("history") {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(history).unwrap_or_default()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&data).unwrap_or_default()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ============= Business Logic =============
|
||||
|
||||
fn format_date(dt: chrono::DateTime<Utc>) -> String {
|
||||
dt.format("%Y-%m-%d").to_string()
|
||||
}
|
||||
|
||||
/// Get the target date, adjusted for "save for yesterday" logic (before noon = yesterday)
|
||||
fn get_target_date() -> (String, bool) {
|
||||
let now = Utc::now();
|
||||
let save_for_yesterday = now.hour() < 12;
|
||||
let date = if save_for_yesterday {
|
||||
now - chrono::Duration::days(1)
|
||||
} else {
|
||||
now
|
||||
};
|
||||
if save_for_yesterday {
|
||||
println!(
|
||||
"Saving logs for yesterday because it is {} hours of the day",
|
||||
now.hour()
|
||||
);
|
||||
}
|
||||
(format_date(date), save_for_yesterday)
|
||||
}
|
||||
|
||||
/// Load existing log entry from DB, or return a default empty one
|
||||
fn get_or_create_log(conn: &Connection, date: &str) -> MizeResult<Value> {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT data FROM habitica_log WHERE date = ?1")
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let result: Option<String> = stmt.query_row(params![date], |row| row.get(0)).ok();
|
||||
match result {
|
||||
Some(data_str) => {
|
||||
serde_json::from_str(&data_str).map_err(|e| mize_err!("JSON parse error: {}", e))
|
||||
}
|
||||
None => Ok(json!({
|
||||
"dailies_done": [],
|
||||
"dailies_skipped": [],
|
||||
"todos_done": [],
|
||||
"todos_skipped": []
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Upsert a log entry into the database
|
||||
fn upsert_log(conn: &Connection, date: &str, data: &Value) -> MizeResult<()> {
|
||||
let data_str =
|
||||
serde_json::to_string(data).map_err(|e| mize_err!("JSON serialize error: {}", e))?;
|
||||
conn.execute(
|
||||
"INSERT INTO habitica_log (date, data) VALUES (?1, ?2)
|
||||
ON CONFLICT(date) DO UPDATE SET data=excluded.data",
|
||||
params![date, data_str],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error upserting log: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_habitica_dailies(mize: &mut Mize) -> MizeResult<()> {
|
||||
println!("######### cli bed cmd #########");
|
||||
|
||||
// Fetch dailies from Habitica
|
||||
let dailies = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.get_tasks("dailys")?
|
||||
};
|
||||
|
||||
// Write to database
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
if let Some(arr) = dailies.as_array() {
|
||||
println!("Syncing {} dailies to database...", arr.len());
|
||||
for daily in arr {
|
||||
let id = daily["id"].as_str().unwrap_or("");
|
||||
let text = daily["text"].as_str().unwrap_or("");
|
||||
let notes = daily["notes"].as_str().unwrap_or("");
|
||||
let priority = daily["priority"].to_string();
|
||||
let frequency = daily["frequency"].as_str().unwrap_or("daily");
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO habitica_dailies (id, text, notes, priority, frequency)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
text=excluded.text,
|
||||
notes=excluded.notes,
|
||||
priority=excluded.priority,
|
||||
frequency=excluded.frequency",
|
||||
params![id, text, notes, priority, frequency],
|
||||
)
|
||||
.map_err(|e| mize_err!("DB error writing daily: {}", e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn save_habitica_logs(mize: &mut Mize) -> MizeResult<()> {
|
||||
let (date_str, save_for_yesterday) = get_target_date();
|
||||
|
||||
// Fetch dailies and completed todos from Habitica
|
||||
let (dailies, completed_todos) = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let dailies = hab.get_tasks("dailys")?;
|
||||
let completed_todos = hab.get_tasks("completedTodos")?;
|
||||
(dailies, completed_todos)
|
||||
};
|
||||
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut data = get_or_create_log(conn, &date_str)?;
|
||||
|
||||
// Process dailies
|
||||
if let Some(arr) = dailies.as_array() {
|
||||
for daily in arr {
|
||||
let is_due = if save_for_yesterday {
|
||||
// Look at history for yesterday's status
|
||||
daily["history"]
|
||||
.as_array()
|
||||
.and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0)))
|
||||
.map(|e| e["isDue"].as_bool().unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
daily["isDue"].as_bool().unwrap_or(false)
|
||||
};
|
||||
|
||||
if !is_due {
|
||||
continue;
|
||||
}
|
||||
|
||||
let completed = if save_for_yesterday {
|
||||
daily["history"]
|
||||
.as_array()
|
||||
.and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0)))
|
||||
.map(|e| e["completed"].as_bool().unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
daily["completed"].as_bool().unwrap_or(false)
|
||||
};
|
||||
|
||||
let daily_id = daily["id"].as_str().unwrap_or("");
|
||||
let daily_text = daily["text"].as_str().unwrap_or("");
|
||||
|
||||
if completed {
|
||||
data["dailies_done"]
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.push(json!(daily_id));
|
||||
println!("daily-done: {}", daily_text);
|
||||
} else {
|
||||
data["dailies_skipped"]
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.push(json!(daily_id));
|
||||
println!("daily-skipped: {}", daily_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process completed todos
|
||||
if let Some(arr) = completed_todos.as_array() {
|
||||
let today_str = format_date(Utc::now());
|
||||
for todo in arr {
|
||||
let date_completed_str = todo["dateCompleted"].as_str().unwrap_or("");
|
||||
|
||||
// Parse the completion date (ISO 8601 format from Habitica)
|
||||
let date_completed = chrono::DateTime::parse_from_rfc3339(date_completed_str)
|
||||
.map(|d| format_date(d.with_timezone(&Utc)))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Check if already in the log for this date
|
||||
if save_for_yesterday {
|
||||
let existing_todos = data["todos_done"].as_array().unwrap();
|
||||
let todo_id = todo["id"].as_str().unwrap_or("");
|
||||
if existing_todos
|
||||
.iter()
|
||||
.any(|t| t["id"].as_str() == Some(todo_id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let todo_entry = json!({
|
||||
"id": todo["id"],
|
||||
"text": todo["text"],
|
||||
"notes": todo.get("notes").unwrap_or(&json!("")),
|
||||
"checklist": todo.get("checklist").unwrap_or(&json!([])),
|
||||
"tags": todo.get("tags").unwrap_or(&json!([])),
|
||||
});
|
||||
|
||||
// Save if completed on the target date
|
||||
if date_completed == date_str {
|
||||
println!("todo-done: {}", todo["text"].as_str().unwrap_or(""));
|
||||
data["todos_done"]
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.push(todo_entry.clone());
|
||||
}
|
||||
|
||||
// If saving for yesterday but todo was completed today, also record it
|
||||
if save_for_yesterday && date_completed == today_str {
|
||||
println!("todo-done: {}", todo["text"].as_str().unwrap_or(""));
|
||||
data["todos_done"].as_array_mut().unwrap().push(todo_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upsert_log(conn, &date_str, &data)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn skip_habitica_todo(mize: &mut Mize, num: usize) -> MizeResult<()> {
|
||||
let (date_str, _) = get_target_date();
|
||||
|
||||
// Fetch todos from Habitica
|
||||
let todos = {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.get_tasks("todos")?
|
||||
};
|
||||
|
||||
let arr = todos
|
||||
.as_array()
|
||||
.ok_or_else(|| mize_err!("Expected array of todos"))?;
|
||||
|
||||
// Delete and log each skipped todo
|
||||
for i in 0..num {
|
||||
let todo = match arr.get(i) {
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let todo_id = todo["id"].as_str().unwrap_or("");
|
||||
|
||||
// Delete the todo and score the skip habit
|
||||
{
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
hab.delete_task(todo_id)?;
|
||||
}
|
||||
increment_habit(mize)?;
|
||||
|
||||
// Record in log
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut data = get_or_create_log(conn, &date_str)?;
|
||||
data["todos_skipped"].as_array_mut().unwrap().push(json!({
|
||||
"id": todo["id"],
|
||||
"text": todo["text"],
|
||||
"notes": todo.get("notes").unwrap_or(&json!("")),
|
||||
"checklist": todo.get("checklist").unwrap_or(&json!([])),
|
||||
"tags": todo.get("tags").unwrap_or(&json!([])),
|
||||
}));
|
||||
upsert_log(conn, &date_str, &data)?;
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn increment_habit(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
let habits = hab.get_tasks("habits")?;
|
||||
if let Some(arr) = habits.as_array() {
|
||||
for habit in arr {
|
||||
if habit["text"].as_str() == Some("skipped a planned task") {
|
||||
let id = habit["id"].as_str().unwrap_or("");
|
||||
hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
format!("tasks/{}/score/down", id),
|
||||
json!({}),
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_action_items(mize: &mut Mize) -> MizeResult<()> {
|
||||
let initial_content = "\
|
||||
gu 10min TIMER
|
||||
uw 15min FROG
|
||||
b coffee/tee/kakau
|
||||
uw FROG
|
||||
b breakfast, brush teeth, lüften
|
||||
uw FROG
|
||||
|
||||
lunch
|
||||
|
||||
uw 15min TIMER of next days FROG
|
||||
fw
|
||||
dt 30min TIMER
|
||||
|
||||
fun 20:00 (smth that can be stopped easily)
|
||||
se 21:40
|
||||
read
|
||||
bed 22:10";
|
||||
|
||||
let tmp_file =
|
||||
std::env::temp_dir().join(format!("habitica-tasks-{}.md", Utc::now().timestamp()));
|
||||
std::fs::write(&tmp_file, initial_content)
|
||||
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
|
||||
|
||||
// Open in nvim for editing
|
||||
std::process::Command::new("nvim")
|
||||
.arg(&tmp_file)
|
||||
.status()
|
||||
.map_err(|e| mize_err!("Failed to run nvim: {}", e))?;
|
||||
|
||||
// Read back and parse
|
||||
let list_str = std::fs::read_to_string(&tmp_file)
|
||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||
let _ = std::fs::remove_file(&tmp_file);
|
||||
|
||||
let tasks: Vec<&str> = list_str
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect();
|
||||
|
||||
// Add tasks to Habitica
|
||||
let mut task_ids = Vec::new();
|
||||
{
|
||||
let mut hab = mize.get_part_native::<Habitica>("habitica")?;
|
||||
for todo_text in &tasks {
|
||||
let data = hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
"tasks/user".to_string(),
|
||||
json!({ "text": todo_text, "type": "todo" }),
|
||||
)?;
|
||||
let id = data["id"].as_str().unwrap_or("").to_string();
|
||||
println!("Added to-do: '{}' with id '{}'", todo_text, id);
|
||||
task_ids.push(id);
|
||||
}
|
||||
|
||||
// Move all tasks to bottom
|
||||
for task_id in &task_ids {
|
||||
hab.api_request(
|
||||
reqwest::Method::POST,
|
||||
format!("tasks/{}/move/to/-1", task_id),
|
||||
json!({}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dump_habitica_logs(mize: &mut Mize) -> MizeResult<()> {
|
||||
let c2vi = mize.get_part_native::<C2vi>("c2vi")?;
|
||||
c2vi.with_db(|conn| {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT date, data FROM habitica_log ORDER BY date")
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
||||
})
|
||||
.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
for row in rows {
|
||||
let (date, data_str) = row.map_err(|e| mize_err!("DB error: {}", e))?;
|
||||
let data: Value =
|
||||
serde_json::from_str(&data_str).unwrap_or(json!({"parse_error": data_str}));
|
||||
println!(
|
||||
"{}: {}",
|
||||
date,
|
||||
serde_json::to_string_pretty(&data).unwrap_or_default()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
export function c2vi_obsidian_canvas_patch(mize: any) {
|
||||
const plugin = mize.get_part_native("obsidian");
|
||||
|
||||
window.pc = patchCanvas;
|
||||
window.upc = () => {
|
||||
unpatchCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
};
|
||||
|
||||
console.log("hereeeeeeeeeeeeeeeee");
|
||||
// canvas patching
|
||||
plugin.registerEvent(
|
||||
plugin.app.workspace.on("file-open", (file: any) => {
|
||||
if (file?.extension === "canvas") {
|
||||
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
}
|
||||
}),
|
||||
);
|
||||
plugin.registerEvent(
|
||||
plugin.app.workspace.on("active-leaf-change", (file: any) => {
|
||||
if (file?.extension === "canvas") {
|
||||
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const maybePathCanvas = (plugin: PPCObsidianPlugin, leaf: any | null) => {
|
||||
console.log("maybePathCanvas called");
|
||||
const file = plugin.app.workspace.getActiveFile();
|
||||
|
||||
if (file?.extension !== "canvas") return;
|
||||
console.log("patching canvas.....");
|
||||
|
||||
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
|
||||
if ("isPatched" in canvas) return;
|
||||
|
||||
patchCanvas(plugin, leaf, canvas);
|
||||
};
|
||||
|
||||
function patchCanvas(plugin: PPCObsidianPlugin, leaf: any, canvas: any) {
|
||||
canvas.isPatched = true;
|
||||
const canvasEl = canvas.canvasEl;
|
||||
const canvasWrapperEl = canvasEl.parentElement;
|
||||
const overlay = createDiv();
|
||||
overlay.setAttr("class", "c2vi-canvas-overlay");
|
||||
const canvas_card_menue = canvasWrapperEl.childNodes[1];
|
||||
canvasWrapperEl.insertBefore(overlay, canvas_card_menue);
|
||||
Object.assign(overlay.style, {
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
//zIndex: "1337",
|
||||
});
|
||||
overlay.addEventListener("contextmenu", (e) => {
|
||||
if (e.button === 2) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
overlay.addEventListener("mouseout", (e) => {
|
||||
const menu = document.getElementsByClassName("menu")[0];
|
||||
if (!menu) return;
|
||||
menu.addEventListener("pointerup", (e) => {
|
||||
console.log("pointerup on menu", e);
|
||||
e.target.click();
|
||||
/*
|
||||
menu.dispatchEvent(
|
||||
new MouseEvent("mousedown", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
button: e.button,
|
||||
buttons: e.buttons,
|
||||
view: window,
|
||||
}),
|
||||
);
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
setupEventPropagation("pointerdown", PointerEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("pointerup", PointerEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("mousedown", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("mouseup", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("click", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("dragstart", DragEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("dragstop", DragEvent, overlay, canvasWrapperEl);
|
||||
|
||||
window.a = canvasWrapperEl;
|
||||
window.b = overlay;
|
||||
|
||||
const mouseEvents = [
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"click",
|
||||
"dblclick",
|
||||
//"mousemove",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"wheel",
|
||||
];
|
||||
|
||||
mouseEvents.forEach((eventType) => {
|
||||
canvasWrapperEl.addEventListener(
|
||||
eventType,
|
||||
(event) => {
|
||||
console.log(`Mouse Event Triggered: ${event.type}`, event);
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function unpatchCanvas(plugin: PPCObsidianPlugin, leaf: any) {
|
||||
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
|
||||
if (!canvas.isPatched) return;
|
||||
canvas.isPatched = false;
|
||||
const canvasEl = canvas.canvasEl;
|
||||
const canvasWrapperEl = canvasEl.parentElement;
|
||||
|
||||
const targets = canvasEl.querySelectorAll(":scope > .c2vi-canvas-overlay");
|
||||
|
||||
targets.forEach((child) => child.remove());
|
||||
}
|
||||
|
||||
function setupEventPropagation(type: string, evClass, overlay: any, target) {
|
||||
overlay.addEventListener(type, (originalEvent) => {
|
||||
originalEvent.stopPropagation();
|
||||
originalEvent.stopImmediatePropagation();
|
||||
originalEvent.preventDefault();
|
||||
const newEvent = new evClass(type, originalEvent);
|
||||
if (type == "pointerdown" && originalEvent.ctrlKey == true) {
|
||||
dispatchContextMenue(
|
||||
target,
|
||||
originalEvent.clientX,
|
||||
originalEvent.clientY,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (type === "pointerdown") {
|
||||
window.downEv = newEvent;
|
||||
}
|
||||
if (type === "pointerup") {
|
||||
window.upEv = newEvent;
|
||||
}
|
||||
newEvent.isTrusted = true;
|
||||
console.log("original Event of", type, originalEvent);
|
||||
console.log("new Event of", type, newEvent);
|
||||
target.dispatchEvent(newEvent);
|
||||
});
|
||||
}
|
||||
|
||||
function dispatchContextMenue(target, x, y) {
|
||||
const newEvent = new PointerEvent("contextmenu", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
view: window,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
pointerType: "mouse",
|
||||
button: 2,
|
||||
buttons: 2,
|
||||
pointerId: 1,
|
||||
isPrimary: true,
|
||||
});
|
||||
target.dispatchEvent(newEvent);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
use clap::{ArgMatches, Command};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mize::{mize_part, Mize, MizePart, MizeResult};
|
||||
|
||||
#[mize_part("cli")]
|
||||
#[derive(Default)]
|
||||
pub struct CliPart {
|
||||
mize: Mize,
|
||||
cmd: Option<Command>,
|
||||
actions: HashMap<
|
||||
String,
|
||||
Box<dyn FnOnce(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>,
|
||||
>,
|
||||
parsers:
|
||||
Option<Vec<Box<dyn FnOnce(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>>>,
|
||||
}
|
||||
|
||||
pub fn cli(mize: &mut Mize) -> MizeResult<()> {
|
||||
let command = Command::new("marts-cli").allow_external_subcommands(true);
|
||||
let cli_part = CliPart {
|
||||
mize: mize.clone(),
|
||||
cmd: Some(command),
|
||||
actions: HashMap::new(),
|
||||
parsers: Some(Vec::new()),
|
||||
};
|
||||
|
||||
mize.add_part(Box::new(cli_part))
|
||||
}
|
||||
|
||||
impl MizePart for CliPart {
|
||||
fn init(&mut self, _mize: &mut Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
let matches = self.cmd.take().unwrap().get_matches();
|
||||
let sub_cmd = match matches.subcommand_name() {
|
||||
Some(sub_cmd) => sub_cmd,
|
||||
None => {
|
||||
println!("No subcommand provided");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let action = match self.actions.remove(sub_cmd) {
|
||||
Some(action) => action,
|
||||
None => {
|
||||
// external parsers
|
||||
for parser in self.parsers.take().unwrap() {
|
||||
parser(
|
||||
mize.clone(),
|
||||
matches
|
||||
.subcommand_matches(sub_cmd)
|
||||
.unwrap()
|
||||
.get_many::<String>("")
|
||||
.unwrap()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
action(matches.subcommand_matches(sub_cmd).unwrap(), mize.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("cli.name");
|
||||
}
|
||||
}
|
||||
|
||||
impl CliPart {
|
||||
pub fn subcommand<T: FnOnce(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
subcmd: Command,
|
||||
action: T,
|
||||
) -> &mut Command {
|
||||
let name = subcmd.get_name().to_string();
|
||||
let cmd = self.cmd.take().unwrap().subcommand(subcmd);
|
||||
self.actions.insert(name, Box::new(action));
|
||||
self.cmd = Some(cmd);
|
||||
self.cmd.as_mut().unwrap()
|
||||
}
|
||||
pub fn add_sub_parser<
|
||||
T: FnOnce(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static,
|
||||
>(
|
||||
&mut self,
|
||||
parser: T,
|
||||
) -> MizeResult<()> {
|
||||
self.parsers.as_mut().unwrap().push(Box::new(parser));
|
||||
Ok(())
|
||||
}
|
||||
pub fn with_cmd(
|
||||
&mut self,
|
||||
func: fn(mize: Mize, cmd: Command) -> MizeResult<Command>,
|
||||
) -> MizeResult<()> {
|
||||
let cmd = func(self.mize.clone(), self.cmd.take().unwrap())?;
|
||||
self.cmd = Some(cmd);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "@ppc/marts",
|
||||
"imports": {},
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"tasks": {}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
(() => {
|
||||
// habitica.ts
|
||||
var opts = {
|
||||
"habitica.api_url": {
|
||||
env_var_name: "PPC_HABITICA_API_URL",
|
||||
default_val: "https://habitica.com/api/v3"
|
||||
},
|
||||
"habitica.user_id": {
|
||||
env_var_name: "PPC_HABITICA_USER_ID"
|
||||
},
|
||||
"habitica.client_name": {
|
||||
default_val: "3544a0b8-d71a-46e0-9bb1-6ddbb2abcddb-PPC-Software"
|
||||
},
|
||||
"habitica.api_token": {
|
||||
env_var_name: "PPC_HABITICA_API_TOKEN"
|
||||
}
|
||||
};
|
||||
async function habitica(mize) {
|
||||
Deno.core.print("hiiiiiiiiiiiiiiiiiiii from habitica");
|
||||
log("hiiiiiiiiiiiiiiiiiiii");
|
||||
mize.add_opts(opts);
|
||||
mize.add_part(new Habitica(mize));
|
||||
}
|
||||
async function handleRateLimit(response) {
|
||||
const limit = response.headers.get("X-RateLimit-Limit") || "NONE";
|
||||
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining") || "10", 10);
|
||||
const reset = response.headers.get("X-RateLimit-Reset");
|
||||
console.log(`RateLimit: ${limit} | Remaining: ${remaining} | Reset: ${reset}`);
|
||||
if (remaining < 2 && reset) {
|
||||
const resetDate = new Date(reset);
|
||||
const now = /* @__PURE__ */ new Date();
|
||||
const waitMs = resetDate.getTime() - now.getTime() + 1e3;
|
||||
if (waitMs > 0) {
|
||||
console.log(`Waiting ${Math.round(waitMs / 1e3)} secs for next rate limit window...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
var Habitica = class {
|
||||
mize;
|
||||
constructor(mize) {
|
||||
this.mize = mize;
|
||||
}
|
||||
async get_tasks(type = "todos") {
|
||||
return await this.api_request("GET", `tasks/user?type=${type}`);
|
||||
}
|
||||
async delete_task(id) {
|
||||
await this.api_request("DELETE", `tasks/${id}`);
|
||||
}
|
||||
async api_request(method, path, extraHeaders = {}, data = {}) {
|
||||
const mize = this.mize;
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
const headers = Object.assign({
|
||||
"Content-Type": "application/json",
|
||||
"x-api-user": mize.get_config("habitica.user_id"),
|
||||
"x-api-key": mize.get_config("habitica.api_token"),
|
||||
"x-client": mize.get_config("habitica.client_name")
|
||||
}, extraHeaders);
|
||||
const response = await fetch(mize.get_config("habitica.api_url") + path, {
|
||||
method,
|
||||
headers,
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
await handleRateLimit(response);
|
||||
if (!response.ok) {
|
||||
console.log("request_url:", mize.get_config("habitica.api_url") + path);
|
||||
throw new Error(`Habitica API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return (await response.json()).data;
|
||||
}
|
||||
};
|
||||
habitica(globalThis.mize);
|
||||
Deno.core.print("hiiiiiiiiiiiiiiiiiiii from habitica one");
|
||||
})();
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as esbuild from "npm:esbuild";
|
||||
|
||||
import { denoPlugin } from "jsr:@deno/esbuild-plugin";
|
||||
|
||||
await esbuild.build({
|
||||
plugins: [denoPlugin()],
|
||||
entryPoints: ["./habitica.ts"],
|
||||
outfile: "./deno_dist/habitica.js",
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
});
|
||||
esbuild.stop();
|
||||
@@ -1,127 +0,0 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizeResult};
|
||||
use reqwest::{blocking::Client, blocking::Response, header, Method};
|
||||
use serde_json::{json, Value};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct Habitica {
|
||||
mize: Mize,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
pub fn habitica(mize: &mut Mize) -> MizeResult<()> {
|
||||
let client = Client::new();
|
||||
mize.register_part(Box::new(Habitica {
|
||||
mize: mize.clone(),
|
||||
client,
|
||||
}))
|
||||
}
|
||||
|
||||
impl MizePart for Habitica {
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("habitica.api_url");
|
||||
mize.new_opt("habitica.user_id");
|
||||
mize.new_opt("habitica.api_token");
|
||||
mize.new_opt("habitica.client_name");
|
||||
}
|
||||
}
|
||||
|
||||
impl Habitica {
|
||||
pub fn api_request(&mut self, method: Method, path: String, data: Value) -> MizeResult<Value> {
|
||||
let api_url = self.mize.get_config("habitica.api_url")?.to_string();
|
||||
let user_id = self.mize.get_config("habitica.user_id")?.to_string();
|
||||
let api_token = self.mize.get_config("habitica.api_token")?.to_string();
|
||||
let client_name = self.mize.get_config("habitica.client_name")?.to_string();
|
||||
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(
|
||||
"Content-Type",
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
);
|
||||
headers.insert("x-api-user", header::HeaderValue::from_str(&user_id)?);
|
||||
headers.insert("x-api-key", header::HeaderValue::from_str(&api_token)?);
|
||||
headers.insert("x-client", header::HeaderValue::from_str(&client_name)?);
|
||||
|
||||
let url = format!("{}/{}", api_url, path.trim_start_matches('/'));
|
||||
|
||||
let mut request_builder = self.client.request(method, &url).headers(headers);
|
||||
|
||||
if data != json!({}) {
|
||||
request_builder = request_builder.json(&data);
|
||||
}
|
||||
|
||||
let response = request_builder.send()?;
|
||||
|
||||
handle_rate_limit(&response);
|
||||
|
||||
if !response.status().is_success() {
|
||||
let status = response.status();
|
||||
let text = response.text().unwrap_or_default();
|
||||
println!("request_url: {}", url);
|
||||
return Err(mize_err!(
|
||||
"Habitica API error: {} {} - {}",
|
||||
status.as_u16(),
|
||||
status.canonical_reason().unwrap_or(""),
|
||||
text
|
||||
));
|
||||
}
|
||||
|
||||
let json_response: Value = response.json()?;
|
||||
Ok(json_response.get("data").cloned().unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
pub fn get_tasks(&mut self, task_type: &str) -> MizeResult<Value> {
|
||||
self.api_request(
|
||||
Method::GET,
|
||||
format!("tasks/user?type={}", task_type),
|
||||
json!({}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete_task(&mut self, id: &str) -> MizeResult<Value> {
|
||||
self.api_request(Method::DELETE, format!("tasks/{}", id), json!({}))
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_rate_limit(response: &Response) {
|
||||
let headers = response.headers();
|
||||
let limit = headers
|
||||
.get("X-RateLimit-Limit")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("NONE");
|
||||
let remaining = headers
|
||||
.get("X-RateLimit-Remaining")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|s| s.parse::<i32>().ok())
|
||||
.unwrap_or(10);
|
||||
let reset = headers
|
||||
.get("X-RateLimit-Reset")
|
||||
.and_then(|v| v.to_str().ok());
|
||||
|
||||
println!(
|
||||
"RateLimit: {} | Remaining: {} | Reset: {}",
|
||||
limit,
|
||||
remaining,
|
||||
reset.unwrap_or("N/A")
|
||||
);
|
||||
|
||||
if remaining < 2 {
|
||||
if let Some(reset_str) = reset {
|
||||
if let Ok(reset_date) = DateTime::parse_from_rfc2822(reset_str) {
|
||||
let now = Utc::now();
|
||||
let wait_duration = reset_date.signed_duration_since(now).to_std();
|
||||
if let Ok(wait_duration) = wait_duration {
|
||||
let wait_ms = wait_duration.as_millis() as u64 + 1000;
|
||||
println!(
|
||||
"Waiting {} secs for next rate limit window...",
|
||||
(wait_ms / 1000)
|
||||
);
|
||||
sleep(Duration::from_millis(wait_ms));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./task/obsidian-otask-parse";
|
||||
export * from "./c2vi/obsidian-canvas-patch";
|
||||
|
||||
export * from "./js/stub_deno";
|
||||
@@ -1,53 +0,0 @@
|
||||
use deno_core::extension;
|
||||
use deno_core::op2;
|
||||
use deno_core::OpState;
|
||||
use mize::{Mize, MizeError};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::CliPart;
|
||||
use crate::JsPart;
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_mize_get_part(#[string] name: &str) {
|
||||
println!("js wants the part {}", name);
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
fn op_mize_get_config(state: &mut OpState, #[string] key: &str) -> String {
|
||||
let mize: Mize = state.borrow::<Mize>().clone();
|
||||
format!("config_value_for_{}", key)
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_mize_add_part(state: &mut OpState, #[string] key: &str) {
|
||||
let mize: Mize = state.borrow::<Mize>().clone();
|
||||
println!("adding part {}", key);
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_cli_subcommand(state: &mut OpState, #[string] name: String) {
|
||||
let mut mize: Mize = state.borrow::<Mize>().clone();
|
||||
let mut cli = mize.get_part_native::<CliPart>("cli").unwrap();
|
||||
cli.subcommand(
|
||||
clap::Command::new(name.clone()),
|
||||
move |_sub_matches, mut mize| {
|
||||
let mut js = mize.get_part_native::<JsPart>("js").unwrap();
|
||||
js.eval("log(\"cli command being called\")".to_string())?;
|
||||
js.eval(format!("mize.vars.cli.subcommand({name})"))?;
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Create an extension with your custom ops
|
||||
extension!(
|
||||
my_extension,
|
||||
ops = [op_mize_get_part, op_mize_get_config],
|
||||
options = {
|
||||
mize: Mize,
|
||||
},
|
||||
state = |state: &mut OpState, config: Config| {
|
||||
state.put::<Mize>(config.mize);
|
||||
},
|
||||
);
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Mize } from "./stub_deno.ts";
|
||||
|
||||
function argsToMessage(...args) {
|
||||
return args.map((arg) => JSON.stringify(arg)).join(" ");
|
||||
}
|
||||
|
||||
globalThis.log = (...args) => {
|
||||
Deno.core.print(`[out]: ${argsToMessage(...args)}\n`, false);
|
||||
};
|
||||
|
||||
globalThis.mize = new Mize();
|
||||
@@ -1,136 +0,0 @@
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::v8::OneByteConst;
|
||||
use deno_core::{
|
||||
ascii_str_include, extension, op2, JsRuntime, ModuleSpecifier, PollEventLoopOptions,
|
||||
RuntimeOptions,
|
||||
};
|
||||
use deno_core::{FastStaticString, FsModuleLoader};
|
||||
use mize::async_trait;
|
||||
use mize::instance::MizePartCreate;
|
||||
use mize::{mize_part, Mize, MizePart, MizeResult};
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::task::{spawn_local, LocalSet};
|
||||
|
||||
mod glue_deno;
|
||||
|
||||
#[mize_part("js")]
|
||||
#[derive(Default)]
|
||||
pub struct JsPart {
|
||||
mize: Mize,
|
||||
sender: Option<flume::Sender<JsRuntimeThreadMessage>>,
|
||||
}
|
||||
|
||||
pub type BoxFuture<'a> = Pin<Box<dyn Future<Output = MizeResult<()>> + 'a>>;
|
||||
pub type BoxClosure = Box<dyn for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a> + Send + 'static>;
|
||||
|
||||
impl MizePart for JsPart {
|
||||
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
println!("js part run");
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(JsRuntimeThreadMessage::DoRunPhase)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn js(mize: &mut Mize) -> MizeResult<()> {
|
||||
let (sender, receiver) = flume::unbounded::<JsRuntimeThreadMessage>();
|
||||
let mize_clone = mize.clone();
|
||||
|
||||
// the thread which will run any js
|
||||
mize.spawn_and_wait("js_runtime_thread", || {
|
||||
js_runtime_thread(mize_clone, receiver)
|
||||
})?;
|
||||
|
||||
mize.add_part(Box::new(JsPart {
|
||||
mize: mize.clone(),
|
||||
sender: Some(sender),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn part_from_file(
|
||||
mize: &mut Mize,
|
||||
name: &'static str,
|
||||
code: FastStaticString,
|
||||
) -> MizeResult<()> {
|
||||
let mut js = mize.get_part_native::<JsPart>("js")?;
|
||||
js.execute_init_js(code)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl JsPart {
|
||||
pub fn execute_init_js(&mut self, code: FastStaticString) -> MizeResult<()> {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(JsRuntimeThreadMessage::RunInitJs(code))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
pub fn eval(&mut self, code: String) -> MizeResult<()> {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(JsRuntimeThreadMessage::RunJs(code))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn js_runtime_thread(
|
||||
mize: Mize,
|
||||
receiver: flume::Receiver<JsRuntimeThreadMessage>,
|
||||
) -> MizeResult<()> {
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
//module_loader: Some(Rc::new(FsModuleLoader)),
|
||||
//extensions: vec![glue_deno::my_extension::init(mize.clone())],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
js_runtime
|
||||
.execute_script("[stub]", ascii_str_include!("../deno_dist/glue_deno.js"))
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
println!("js thread waiting for smth");
|
||||
let msg = receiver.recv().unwrap();
|
||||
println!("js thread got smth");
|
||||
match msg {
|
||||
JsRuntimeThreadMessage::RunInitJs(js_code) => {
|
||||
if let Err(err) = js_runtime.execute_script("[init]", js_code) {
|
||||
println!("err: {}", err);
|
||||
}
|
||||
println!("done running js");
|
||||
}
|
||||
JsRuntimeThreadMessage::RunJs(js_code) => {
|
||||
if let Err(err) = js_runtime.execute_script("[idk]", js_code) {
|
||||
println!("err: {}", err);
|
||||
}
|
||||
println!("done running js");
|
||||
}
|
||||
JsRuntimeThreadMessage::DoRunPhase => {
|
||||
if let Err(err) = js_runtime.execute_script("[runPhase]", "mize.runPhase()") {
|
||||
println!("err: {}", err);
|
||||
}
|
||||
println!("done with runPhase");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum JsRuntimeThreadMessage {
|
||||
//Closure(Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>),
|
||||
//AsyncClosure(BoxClosure),
|
||||
RunInitJs(FastStaticString),
|
||||
RunJs(String),
|
||||
DoRunPhase,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export class Mize {
|
||||
async get_config(key: string): Promise<any> {
|
||||
return "hiiiiiii";
|
||||
}
|
||||
async add_opts(opts: any): Promise<void> {}
|
||||
async add_part(part: any): Promise<void> {}
|
||||
async report_err(err: MizeError) {}
|
||||
runPhase() {
|
||||
Deno.core.print("runPhase in js \n");
|
||||
}
|
||||
}
|
||||
|
||||
type MizeError = string;
|
||||
|
||||
export class Cli {
|
||||
constructor(mize: Mize) {}
|
||||
async sub_command(name: string, cmd: any): Promise<void> {}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
use mize::MizeResult;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
mod cli;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use cli::*;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod habitica;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use habitica::*;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod c2vi;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use c2vi::*;
|
||||
|
||||
//#[cfg(feature = "target-os")]
|
||||
//pub mod js;
|
||||
//#[cfg(feature = "target-os")]
|
||||
//pub use js::*;
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "@ppc/marts",
|
||||
"version": "1.0.0",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@flowershow/remark-wiki-link": "^3.4.0",
|
||||
"builtin-modules": "^5.0.0",
|
||||
"esbuild": "^0.27.3",
|
||||
"mdast": "^3.0.0",
|
||||
"obsidian": "^1.12.3",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkStringify from "remark-stringify";
|
||||
import { unified } from "unified";
|
||||
import { visit } from "unist-util-visit";
|
||||
import { Root, Content, Heading, List, ListItem, Text } from "mdast";
|
||||
import remarkFrontmatter from "remark-frontmatter";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import wikiLinkPlugin from "@flowershow/remark-wiki-link";
|
||||
import { TFile } from "obsidian";
|
||||
|
||||
const OTASK_PROP_NAMES = ["short", "outcome", "priority"];
|
||||
const ACTION_PROP_NAMES = [];
|
||||
|
||||
const REMARK = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkFrontmatter)
|
||||
.use(remarkGfm)
|
||||
.use(remarkStringify, { bullet: "-" });
|
||||
|
||||
export function task_obsidian_otask_parse(mize: any) {
|
||||
const obsidian_plugin = mize.get_part_native("obsidian");
|
||||
|
||||
obsidian_plugin.addCommand({
|
||||
id: "full-parse",
|
||||
name: "Parse all otask files into the db",
|
||||
callback: async () => {
|
||||
await full_parse();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function full_parse() {
|
||||
console.log("full parse.......................... yay");
|
||||
let otasks = [];
|
||||
OTASK_PARSE_QUEUE = [];
|
||||
OTASK_LIST = [];
|
||||
|
||||
const root_file = app.vault.getAbstractFileByPath("task/my-projects.md");
|
||||
|
||||
const content = await app.vault.read(root_file);
|
||||
const list = await getListUnderHeading(content, "root");
|
||||
|
||||
for (const entry of list) {
|
||||
queue_otasks_from_list_item(entry, []);
|
||||
}
|
||||
|
||||
// go through the queue of otaskParseTasks
|
||||
// using a queue instead of recursive calls, to make it more debuggable...
|
||||
do {
|
||||
const task = OTASK_PARSE_QUEUE.shift();
|
||||
dbg("task", task);
|
||||
if (task.file) {
|
||||
const otask = await otasks_from_file(
|
||||
task.file,
|
||||
task.otask_path,
|
||||
task.priority,
|
||||
);
|
||||
dbg("otask", otask);
|
||||
if (otask) {
|
||||
OTASK_LIST.push(otask);
|
||||
}
|
||||
} else {
|
||||
const otask = await otasks_from_list_item(
|
||||
task.listItem,
|
||||
task.otask_path,
|
||||
task.priority,
|
||||
);
|
||||
dbg("otask", otask);
|
||||
if (otask) {
|
||||
OTASK_LIST.push(otask);
|
||||
}
|
||||
}
|
||||
} while (OTASK_PARSE_QUEUE.length > 0);
|
||||
|
||||
console.log("otasks::", OTASK_LIST);
|
||||
|
||||
render_sub0_file(OTASK_LIST);
|
||||
}
|
||||
|
||||
let OTASK_PARSE_QUEUE: any = [];
|
||||
let OTASK_LIST = [];
|
||||
let DBG = false;
|
||||
|
||||
function dbg() {
|
||||
if (DBG) {
|
||||
console.log(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
function queue_otasks_from_list_item(listItem, otask_path, priority = 0) {
|
||||
OTASK_PARSE_QUEUE.push({
|
||||
listItem,
|
||||
otask_path,
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
function queue_otasks_from_file(file, otask_path, priority = 0) {
|
||||
OTASK_PARSE_QUEUE.push({
|
||||
file,
|
||||
otask_path,
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
// parsing helper functions
|
||||
|
||||
async function render_sub0_file(otasks) {
|
||||
const sub0_file = app.vault.getAbstractFileByPath("private/sub0-tasks.md");
|
||||
if (!(sub0_file instanceof TFile)) {
|
||||
console.error(
|
||||
"SystemC2: sub0_file not found:",
|
||||
"private/sub0-tasks.md",
|
||||
sub0_file,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//render sub0
|
||||
const sub0_otasks = otasks.filter(
|
||||
(otask) => otask.priority == 0 && !otask.is_file,
|
||||
);
|
||||
console.log("sub0 otasks...", sub0_otasks);
|
||||
|
||||
let rendered_otasks_string = "";
|
||||
|
||||
for (const otask of sub0_otasks) {
|
||||
const path_part = otask.path.length > 0 ? otask.path.join(": ") + ": " : "";
|
||||
|
||||
let content_part = otask.content ? otask.content + "\n" : "";
|
||||
content_part = content_part
|
||||
.split("\n")
|
||||
.map((line) => "\t" + line)
|
||||
.join("\n"); // add a \t in front of every otask.content line
|
||||
|
||||
rendered_otasks_string += `- ${path_part}${otask.longName}\n${content_part}\n`;
|
||||
}
|
||||
//console.log("newContent...", rendered_otasks_string)
|
||||
|
||||
await replaceHeading(sub0_file, "sub0", rendered_otasks_string);
|
||||
|
||||
//render sub-1
|
||||
}
|
||||
|
||||
async function replaceHeading(
|
||||
file: TFile,
|
||||
heading: string,
|
||||
newContent: string,
|
||||
) {
|
||||
const data = await this.app.vault.read(file);
|
||||
|
||||
// Parse the Markdown into an AST
|
||||
const tree = REMARK.parse(data) as Root;
|
||||
|
||||
// Split new content into AST nodes
|
||||
const newContentAst = REMARK.parse(newContent) as Root;
|
||||
|
||||
const children = tree.children;
|
||||
const newChildren: Content[] = [];
|
||||
let insideTarget = false;
|
||||
let targetLevel = 0;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const node = children[i];
|
||||
|
||||
if (node.type === "heading") {
|
||||
const headingNode = node as Heading;
|
||||
const text = headingNode.children
|
||||
.filter((c) => c.type === "text")
|
||||
.map((c: any) => c.value)
|
||||
.join("");
|
||||
|
||||
// If we encounter our target heading
|
||||
if (text.trim() === heading) {
|
||||
insideTarget = true;
|
||||
targetLevel = headingNode.depth;
|
||||
newChildren.push(node); // Keep the heading itself
|
||||
// Insert new content right after
|
||||
newChildren.push(...newContentAst.children);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we’re currently inside target section and see a heading of same or higher level
|
||||
if (insideTarget && headingNode.depth <= targetLevel) {
|
||||
insideTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we’re not inside the target section, keep the node
|
||||
if (!insideTarget) {
|
||||
newChildren.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
const newTree: Root = { ...tree, children: newChildren };
|
||||
|
||||
// Convert AST back to Markdown
|
||||
const newMarkdown = await REMARK.stringify(newTree);
|
||||
|
||||
await this.app.vault.modify(file, newMarkdown);
|
||||
}
|
||||
|
||||
async function otasks_from_list_item(listItem, otask_path, priority = 0) {
|
||||
try {
|
||||
// the main otask this listItem is about
|
||||
let otask = {
|
||||
description: [],
|
||||
actions: [],
|
||||
path: [],
|
||||
priority,
|
||||
};
|
||||
dbg("SystemC2: otasks_from_list_item", listItem, otask_path);
|
||||
|
||||
if (!listItem.children[0].type === "paragraph") {
|
||||
dbg(
|
||||
"listItem[0] of a otask item is not a paragraph... the otask:",
|
||||
listItem,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
let longName = nodeToString(listItem.children[0]);
|
||||
|
||||
dbg("longName:", longName);
|
||||
|
||||
// check for [[anotherotask]], call otasks_from_file, return those
|
||||
const match = longName.match(/\[\[([^\]]+)\]\]/);
|
||||
if (match) {
|
||||
const file = app.metadataCache.getFirstLinkpathDest(match[1], "");
|
||||
if (file) {
|
||||
const metadata = app.metadataCache.getFileCache(file);
|
||||
|
||||
if (metadata?.frontmatter?.tags?.includes("t/otask")) {
|
||||
queue_otasks_from_file(file, otask_path);
|
||||
return null;
|
||||
} else {
|
||||
dbg(
|
||||
"SystemC2: listItem with Link to '",
|
||||
match[1],
|
||||
"', which is not an otask",
|
||||
longName,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check for [x] and remove from name
|
||||
if (longName.startsWith("[ ") || longName.startsWith("[x")) {
|
||||
longName = longName.slice(4);
|
||||
}
|
||||
|
||||
//check for "ot:" and remove from name
|
||||
if (longName.startsWith("ot:")) {
|
||||
longName = longName.slice(4);
|
||||
}
|
||||
|
||||
otask.longName = longName;
|
||||
otask.short = longName;
|
||||
otask.path = otask_path;
|
||||
let new_otask_path = [...otask_path, otask.short];
|
||||
|
||||
// go through sub list items
|
||||
// - if it's a prop for the otask... handle that
|
||||
// - if it's an "ac:" handle that
|
||||
// - if it's "ot: " handle as another otask -> recursive call
|
||||
// - if it's "[ ]" handle as another otask -> recursive call
|
||||
// - if it's a link to a otask file... handle as another otask -> call otasks_from_file()
|
||||
// - else add it as description item list to otask
|
||||
if (!listItem.children || listItem.children.length < 2) {
|
||||
return otask;
|
||||
}
|
||||
|
||||
// add content as text to otask
|
||||
if (otask.longName == "hosting updates") {
|
||||
console.log("listItem... ", listItem.children[1]);
|
||||
let hii = REMARK.stringify(listItem.children[1]);
|
||||
console.log("text...", hii);
|
||||
}
|
||||
let content_text = REMARK.stringify(listItem.children[1]);
|
||||
otask.content = content_text;
|
||||
|
||||
for (const subListItem of listItem.children[1].children) {
|
||||
let text = nodeToString(subListItem.children[0]);
|
||||
|
||||
// check prop
|
||||
if (check_otask_prop_from_list_item(subListItem, otask)) {
|
||||
new_otask_path = [...otask_path, otask.short]; // have to redo it here, in case short changed from aprop
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// remove "[ ]"
|
||||
if (text.startsWith("[ ") || text.startsWith("[x")) {
|
||||
text = text.slice(4);
|
||||
}
|
||||
|
||||
// check ac
|
||||
if (text.startsWith("ac:")) {
|
||||
otask.actions = [
|
||||
...otask.actions,
|
||||
action_from_list_item(subListItem, new_otask_path, otask.priority),
|
||||
];
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check ot
|
||||
if (text.startsWith("ot:")) {
|
||||
queue_otasks_from_list_item(
|
||||
subListItem,
|
||||
new_otask_path,
|
||||
otask.priority,
|
||||
);
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check [ ]
|
||||
if (text.startsWith("[ ] ") || text.startsWith("[x] ")) {
|
||||
queue_otasks_from_list_item(
|
||||
subListItem,
|
||||
new_otask_path,
|
||||
otask.priority,
|
||||
);
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check link to otask file
|
||||
const match = text.match(/\[\[([^\]]+)\]\]/);
|
||||
if (match) {
|
||||
const file = app.metadataCache.getFirstLinkpathDest(match[1], "");
|
||||
if (file) {
|
||||
const metadata = app.metadataCache.getFileCache(file);
|
||||
|
||||
if (metadata?.frontmatter?.tags?.includes("t/otask")) {
|
||||
queue_otasks_from_file(file, new_otask_path, otask.priority);
|
||||
continue;
|
||||
} else {
|
||||
dbg(
|
||||
"SystemC2: listItem with Link to '",
|
||||
match[1],
|
||||
"', which is not an otask",
|
||||
longName,
|
||||
);
|
||||
}
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
}
|
||||
|
||||
// add as description
|
||||
const desc = REMARK.stringify(subListItem);
|
||||
otask.description.push(desc);
|
||||
}
|
||||
|
||||
return otask;
|
||||
} catch (error) {
|
||||
console.log("failed to parse list item:", listItem);
|
||||
}
|
||||
}
|
||||
|
||||
async function otasks_from_file(file: TFile, otask_path, priority = 0) {
|
||||
try {
|
||||
dbg("SystemC2: otasks_from_file", file, otask_path);
|
||||
|
||||
// the main otask this file is about
|
||||
let otask = {
|
||||
description: [],
|
||||
actions: [],
|
||||
path: [],
|
||||
priority,
|
||||
is_file: true,
|
||||
};
|
||||
|
||||
otask.longName = file.basename;
|
||||
otask.short = otask.longName;
|
||||
otask.path = otask_path;
|
||||
let new_otask_path = [...otask_path, otask.short];
|
||||
|
||||
const content = await app.vault.read(file);
|
||||
|
||||
// parse listItems at the top... for props
|
||||
const topListItems = getListItemsBeforeFirstHeading(content);
|
||||
for (const topListItem of topListItems) {
|
||||
const text = nodeToString(topListItem);
|
||||
|
||||
// check for prop
|
||||
if (check_otask_prop_from_list_item(topListItem, otask)) {
|
||||
new_otask_path = [...otask_path, otask.short];
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
}
|
||||
|
||||
// parse all the sub sections
|
||||
const SUB_COUNT = 10;
|
||||
for (let sub_number = 0; sub_number < SUB_COUNT; sub_number++) {
|
||||
let listItems = await getListUnderHeading(content, "sub" + sub_number);
|
||||
|
||||
// "sub" without a number should have priority 5
|
||||
if (sub_number == 5) {
|
||||
listItems = listItems.concat(await getListUnderHeading(content, "sub"));
|
||||
}
|
||||
|
||||
//dbg(`sub section ${sub_number} has items:`, listItems)
|
||||
|
||||
for (const listItem of listItems) {
|
||||
// check if listItem is an action...
|
||||
let text = nodeToString(listItem.children[0]);
|
||||
if (
|
||||
text.startsWith("ac:") ||
|
||||
text.startsWith("[ ] ac:") ||
|
||||
text.startsWith("[x] ac:")
|
||||
) {
|
||||
const action = action_from_list_item(listItem, otask_path);
|
||||
otask.actions.push(action);
|
||||
}
|
||||
|
||||
queue_otasks_from_list_item(
|
||||
listItem,
|
||||
new_otask_path,
|
||||
otask.priority + sub_number,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return otask;
|
||||
} catch (error) {
|
||||
console.log("failed to parse file: ", file.path);
|
||||
}
|
||||
}
|
||||
|
||||
function action_from_list_item(listItem, otask_path) {
|
||||
dbg("SystemC2: action_from_list_item", listItem, otask_path);
|
||||
let action = {};
|
||||
let text = nodeToString(listItem.children[0]).slice(4);
|
||||
|
||||
if (text.startsWith("[")) {
|
||||
text = text.slice(4);
|
||||
}
|
||||
|
||||
action.name = text;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function check_otask_prop_from_list_item(listItem, otask) {
|
||||
let text = nodeToString(listItem.children[0]);
|
||||
|
||||
for (const prop_name of OTASK_PROP_NAMES) {
|
||||
if (text.startsWith(prop_name + ":")) {
|
||||
const value = text.slice(prop_name.length + 2);
|
||||
|
||||
if (prop_name == "priority") {
|
||||
// parse priority as int
|
||||
otask[prop_name] = parseInt(value);
|
||||
} else if (prop_name == "outcome") {
|
||||
// add sub items to it
|
||||
let fullText = REMARK.stringify(listItem).slice(prop_name.length + 4);
|
||||
otask[prop_name] = fullText;
|
||||
} else {
|
||||
otask[prop_name] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract plain text from a node
|
||||
function nodeToString(node: any): string {
|
||||
let result = "";
|
||||
visit(node, "text", (textNode: Text) => {
|
||||
result += textNode.value;
|
||||
});
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
async function getListUnderHeading(
|
||||
markdown: string,
|
||||
headingText: string,
|
||||
): string[] {
|
||||
const tree = REMARK.parse(markdown);
|
||||
//dbg("tree", tree)
|
||||
|
||||
let capture = false;
|
||||
let items: string[] = [];
|
||||
|
||||
for (let i = 0; i < tree.children.length; i++) {
|
||||
const node: Content = tree.children[i];
|
||||
|
||||
// Detect the target heading
|
||||
if (
|
||||
node.type === "heading" &&
|
||||
nodeToString(node).toLowerCase() === headingText.toLowerCase()
|
||||
) {
|
||||
capture = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop if another heading of the same or higher level comes
|
||||
if (capture && node.type === "heading") {
|
||||
break;
|
||||
}
|
||||
|
||||
// Collect list items if capturing
|
||||
if (capture && node.type === "list") {
|
||||
for (const li of (node as List).children) {
|
||||
if (li.type === "listItem") {
|
||||
items.push(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function getListItemsBeforeFirstHeading(markdown: string) {
|
||||
const tree = REMARK.parse(markdown);
|
||||
|
||||
let items = [];
|
||||
|
||||
for (const node of tree.children) {
|
||||
if (node.type === "heading") {
|
||||
// stop at first heading
|
||||
break;
|
||||
}
|
||||
if (node.type === "list") {
|
||||
for (const li of (node as List).children) {
|
||||
if (li.type === "listItem") {
|
||||
items.push(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
2886
packages/mize/Cargo.lock
generated
2886
packages/mize/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "@ppc/mize",
|
||||
"imports": {},
|
||||
"exports": "./src/platform/js/index.ts"
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
|
||||
if [[ "$1" == "a" ]]
|
||||
then
|
||||
ssh acern mkdir -p /tmp/mize-src
|
||||
ssh acern mkdir -p ~/work/modules
|
||||
rsync -rv --exclude="target" --exclude="gitignore" ./* acern:/tmp/mize-src/
|
||||
rsync -rv --exclude="target" --exclude="gitignore" --exclude="node_modules" ~/work/modules acern:work/
|
||||
path=$(ssh acern /home/me/out/nix/bin/nix build /tmp/mize-src#dist -L --print-out-paths)
|
||||
|
||||
echo doneeeeeeeeeeeeeee building
|
||||
echo path: $path
|
||||
|
||||
#scp -r acern:$path/* ocih:host/webserver/webfiles
|
||||
tmp=$(mktemp -d)
|
||||
[[ "$path" != "" ]] && rsync -rv -p --chmod=u+w acern:$path/* $tmp
|
||||
[[ "$path" != "" ]] && rsync -rv $tmp/* ocih:host/data/my-website --rsync-path="sudo rsync"
|
||||
|
||||
|
||||
|
||||
elif [[ "$1" == "c" ]] # only copy files to acern
|
||||
then
|
||||
ssh acern mkdir -p /tmp/mize-src
|
||||
ssh acern mkdir -p ~/work/modules
|
||||
rsync -rv --exclude="target" --exclude="gitignore" ./* acern:/tmp/mize-src/
|
||||
rsync -rv --exclude="target" --exclude="gitignore" --exclude="node_modules" ~/work/modules acern:work/
|
||||
|
||||
|
||||
|
||||
else
|
||||
path=$(nix build .#dist -L -v --print-out-paths $@)
|
||||
|
||||
echo doneeeeeeeeeeeeeee building
|
||||
echo path: $path
|
||||
|
||||
#scp -r $path/* ocih:host/webserver/webfiles
|
||||
[[ "$path" != "" ]] && rsync -rv $path/* ocih:host/data/my-website --rsync-path="sudo rsync"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
nix develop .#mizeFor.x86_64-linux-gnu.main.devShell --override-input nixpkgs github:NixOS/nixpkgs/ea6033ce4ddf2c0ede2ea147a450d4f880796128
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
#set -e
|
||||
|
||||
# this is a Victorinix Wrapper
|
||||
source $($VIC_BINARY print-wrapper-path)
|
||||
|
||||
BINARY=/home/me/work/mize/target/debug/mize
|
||||
|
||||
function vic_build_func() {
|
||||
nix develop ~/work/victorinix --impure -c cargo build --manifest-path ~/work/victorinix/Cargo.toml
|
||||
}
|
||||
|
||||
vic_wrapper_run "$@"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
use crate::mize_err;
|
||||
use crate::MizeResult;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::item::ItemData;
|
||||
use crate::Mize;
|
||||
use crate::MizeError;
|
||||
|
||||
type ConfigThunk = Box<dyn Fn() -> ItemData + Send + Sync>;
|
||||
|
||||
pub struct ConfigOpt {
|
||||
pub name: String,
|
||||
pub val: Option<ItemData>,
|
||||
pub thunk: Option<ConfigThunk>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigOptNameAndMize {
|
||||
pub name: String,
|
||||
pub mize: Mize,
|
||||
}
|
||||
|
||||
impl ConfigOptNameAndMize {
|
||||
pub fn default_val(self, val: ItemData) -> ConfigOptNameAndMize {
|
||||
let mut config_opts = self.mize.config_opts.lock().unwrap();
|
||||
let opt = config_opts.get_mut(&self.name).unwrap();
|
||||
opt.val = Some(val);
|
||||
self.clone()
|
||||
}
|
||||
pub fn fun(self, thunk: ConfigThunk) -> Self {
|
||||
let mut config_opts = self.mize.config_opts.lock().unwrap();
|
||||
let opt = config_opts.get_mut(&self.name).unwrap();
|
||||
opt.thunk = Some(thunk);
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gather_config(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mut config_opts = mize.config_opts.lock().unwrap();
|
||||
let parts = mize.parts.lock().unwrap();
|
||||
|
||||
for part in parts.values() {
|
||||
part.as_deref().unwrap().opts(&mut mize.clone());
|
||||
}
|
||||
|
||||
// populate values from config files
|
||||
let config_file_paths = env::var("MIZE_CONFIG_FILES")?;
|
||||
for config_file_path in config_file_paths.split(":") {
|
||||
println!("reading config file: {config_file_path}");
|
||||
let content = std::fs::read_to_string(config_file_path)?;
|
||||
let mut data = ItemData::from_toml(content.as_str())?;
|
||||
println!("config data: {data}");
|
||||
for path in data.get_paths_recursive()? {
|
||||
let mut conf_name = path.replace("/", ".");
|
||||
if conf_name.starts_with(".") {
|
||||
conf_name.remove(0);
|
||||
}
|
||||
println!("adding config {conf_name} from config file {config_file_path}");
|
||||
println!("path: {:?}", path);
|
||||
let val = data.get_path(path.split("/").filter(|s| s != &"").collect::<Vec<&str>>())?;
|
||||
match config_opts.get_mut(&conf_name) {
|
||||
Some(opt) => {
|
||||
opt.val = Some(val);
|
||||
}
|
||||
None => {
|
||||
config_opts.insert(
|
||||
conf_name.clone(),
|
||||
ConfigOpt {
|
||||
name: conf_name,
|
||||
val: Some(val),
|
||||
thunk: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// https://users.rust-lang.org/t/casting-between-trait-object-types/97220/2
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
||||
use crate::error::MizeResult;
|
||||
use crate::instance::Mize;
|
||||
|
||||
pub trait Module {
|
||||
fn init(&mut self, instance: &Mize) -> MizeResult<()>;
|
||||
|
||||
fn exit(&mut self, instance: &Mize) -> MizeResult<()>;
|
||||
|
||||
fn clone_module(&self) -> Box<dyn Module + Send + Sync>;
|
||||
|
||||
// extra traits, that can be implemented
|
||||
// return None if not implemented
|
||||
|
||||
fn run_cli(&mut self, instance: &Mize, cmd_line: Vec<OsString>) -> Option<MizeResult<()>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// console_log macro
|
||||
// that can be copied into other files for debugging purposes
|
||||
#[cfg(feature = "target-wasm ")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "target-wasm ")]
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm-target")]
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string())})
|
||||
}
|
||||
|
||||
pub struct EmptyModule {}
|
||||
|
||||
impl Module for EmptyModule {
|
||||
fn init(&mut self, instance: &Mize) -> MizeResult<()> {
|
||||
println!("empty module fn init");
|
||||
|
||||
#[cfg(feature = "wasm-target")]
|
||||
console_log!("empty module fn init");
|
||||
Ok(())
|
||||
}
|
||||
fn exit(&mut self, instance: &Mize) -> MizeResult<()> {
|
||||
println!("empty module fn exit");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clone_module(&self) -> Box<dyn Module + Send + Sync> {
|
||||
Box::new(EmptyModule {})
|
||||
}
|
||||
}
|
||||
@@ -1,768 +0,0 @@
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
use std::{collections::HashMap, fmt::Display, io::Cursor, path::PathBuf};
|
||||
use std::{i128, string};
|
||||
use tracing::{debug, error, info, instrument, trace, warn, Instrument};
|
||||
use tracing::{span, Level};
|
||||
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult};
|
||||
use crate::id::MizeId;
|
||||
use crate::instance::connection::value_raw_con_by_id;
|
||||
use crate::instance::store::Store;
|
||||
use crate::instance::{connection, Mize};
|
||||
use crate::mize_err;
|
||||
use crate::proto::MizeMessage;
|
||||
use ciborium::Value as CborValue;
|
||||
|
||||
// a item always has to do with a Instance, which takes care of how it is updated
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Item<'a> {
|
||||
id: MizeId,
|
||||
pub instance: &'a Mize,
|
||||
}
|
||||
|
||||
// without an Instance it is not an item, but only the "data of an item"
|
||||
// and this type for now is just an alias to CborValue
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ItemData(pub CborValue);
|
||||
|
||||
impl Item<'_> {
|
||||
pub fn id(&self) -> MizeId {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
pub fn new(id: MizeId, instance: &Mize) -> Item {
|
||||
Item { id, instance }
|
||||
}
|
||||
|
||||
pub fn value_raw(&self) -> MizeResult<Vec<u8>> {
|
||||
let data = self.as_data_full()?;
|
||||
let raw = get_raw_from_cbor(data.cbor(), vec![])?;
|
||||
return Ok(raw.to_owned());
|
||||
}
|
||||
|
||||
pub fn value_string(&self) -> MizeResult<String> {
|
||||
let raw = self.value_raw()?;
|
||||
String::from_utf8(raw.clone())
|
||||
.mize_result_msg(format!("failed to convert raw value from item '{}' into a utf-8 string. raw value was: '{:02X?}'", self.id(), raw))
|
||||
}
|
||||
|
||||
pub fn as_data_full(&self) -> MizeResult<ItemData> {
|
||||
let id = self.id();
|
||||
|
||||
// the instance we are
|
||||
if id.store_part() == "self" {
|
||||
debug!("getting item '{}' from self", self.id());
|
||||
|
||||
match id.nth_part(1)? {
|
||||
"con_by_id" => {
|
||||
return value_raw_con_by_id(&mut self.clone());
|
||||
}
|
||||
"namespace" => {
|
||||
let namespace_inner = self.instance.namespace.lock()?;
|
||||
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
|
||||
}
|
||||
"self_namespace" => {
|
||||
let namespace_inner = self.instance.self_namespace.lock()?;
|
||||
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
|
||||
}
|
||||
"config" => {
|
||||
let rest_path = id
|
||||
.path
|
||||
.into_iter()
|
||||
.skip(2)
|
||||
.map(|v| v.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join("/");
|
||||
let id_for_store = self
|
||||
.instance
|
||||
.new_id("0/config".to_owned() + "/" + rest_path.as_str())?;
|
||||
let store = self.instance.store.lock()?;
|
||||
let data = store.get_value_data_full(id_for_store)?;
|
||||
return Ok(data);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Err(mize_err!(
|
||||
"a /self path, but the next element in the path '{:?}' is not valid",
|
||||
id.nth_part(1)
|
||||
));
|
||||
}
|
||||
|
||||
let self_namespace_inner = self.instance.self_namespace.lock()?;
|
||||
|
||||
if self.id().namespace() == self_namespace_inner.to_owned() {
|
||||
debug!("getting item '{}' from store", self.id());
|
||||
|
||||
if id.store_part() == "inst" {
|
||||
match id.nth_part(1)? {
|
||||
"con_by_id" => {
|
||||
return value_raw_con_by_id(&mut self.clone());
|
||||
}
|
||||
"namespace" => {
|
||||
let namespace_inner = self.instance.namespace.lock()?;
|
||||
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
|
||||
}
|
||||
"self_namespace" => {
|
||||
let namespace_inner = self.instance.self_namespace.lock()?;
|
||||
return Ok(ItemData::from_string(namespace_inner.as_real_string()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let store_inner = self.instance.store.lock()?;
|
||||
return store_inner.get_value_data_full(self.id());
|
||||
} else {
|
||||
let mut connection = self.instance.get_connection_by_ns(self.id().namespace())?;
|
||||
|
||||
debug!(
|
||||
"getting item '{}' from connection '{}' from store",
|
||||
self.id(),
|
||||
connection.id
|
||||
);
|
||||
|
||||
let msg = MizeMessage::new_get(self.id(), connection.id);
|
||||
connection.send(msg);
|
||||
let data = self.instance.give_msg_wait(self.id())?;
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(name = "fn.ItemData::merge")]
|
||||
pub fn merge<V: Into<ItemData> + Debug>(&mut self, mut value: V) -> MizeResult<()> {
|
||||
let mut data = self.as_data_full()?;
|
||||
//let mut data = data_full.get_path(id_path_without_store_part)?;
|
||||
trace!("item::merge data: {:?}", data);
|
||||
trace!("item::merge id: {:?}", self.id());
|
||||
|
||||
let new_data = value.into();
|
||||
|
||||
data.merge(new_data);
|
||||
trace!("item::merge new_data: {:?}", data);
|
||||
|
||||
// handle the case of /self/*
|
||||
if self.id().store_part() == "self" {
|
||||
let rest_path = self
|
||||
.id()
|
||||
.path
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.map(|v| v.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join("/");
|
||||
let id_for_store = self
|
||||
.instance
|
||||
.new_id("0".to_owned() + "/" + rest_path.as_str())?;
|
||||
|
||||
let store_inner = self.instance.store.lock()?;
|
||||
store_inner.set(id_for_store, data)?;
|
||||
} else if self.instance.we_are_namespace()? {
|
||||
let store_inner = self.instance.store.lock()?;
|
||||
store_inner.set(self.id(), data)?;
|
||||
} else {
|
||||
let namespace = self.id().namespace();
|
||||
let mut connection = self.instance.get_connection_by_ns(namespace)?;
|
||||
let msg = MizeMessage::new_update_request(self.id(), data, connection.id);
|
||||
connection.send(msg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Item<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
//let item_data = self.as_data_full().map_err(|_| std::fmt::Error)?;
|
||||
write!(f, "Item with id: {}", self.id());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemData {
|
||||
pub fn new() -> ItemData {
|
||||
ItemData(CborValue::Null)
|
||||
}
|
||||
|
||||
pub fn empty() -> ItemData {
|
||||
ItemData(CborValue::Null)
|
||||
}
|
||||
|
||||
pub fn from_toml(toml_string: &str) -> MizeResult<ItemData> {
|
||||
let toml_deserializer = toml::Deserializer::new(&toml_string);
|
||||
|
||||
let value = CborValue::deserialize(toml_deserializer).mize_result_msg(format!(
|
||||
"Could not deserialize the toml string: {}",
|
||||
&toml_string
|
||||
))?;
|
||||
|
||||
return Ok(value.into_item_data());
|
||||
}
|
||||
|
||||
pub fn to_toml(&self) -> MizeResult<String> {
|
||||
let mut out_string = String::new();
|
||||
let toml_serializer = toml::Serializer::new(&mut out_string);
|
||||
CborValue::serialize(&self.cbor(), toml_serializer)?;
|
||||
Ok(out_string)
|
||||
}
|
||||
|
||||
pub fn value_string(self) -> MizeResult<String> {
|
||||
match self.cbor() {
|
||||
CborValue::Text(string) => Ok(string.to_owned()),
|
||||
_ => Err(mize_err!("this ItemData has no string value")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_bool(self) -> MizeResult<bool> {
|
||||
match self.cbor() {
|
||||
CborValue::Bool(val) => Ok(*val),
|
||||
_ => Err(mize_err!("this ItemData has no string value")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string<S: Into<String>>(into_string: S) -> ItemData {
|
||||
let string = into_string.into();
|
||||
let data = CborValue::Text(string);
|
||||
return ItemData(data);
|
||||
}
|
||||
|
||||
pub fn to_json(self) -> MizeResult<String> {
|
||||
let mut result: Vec<u8> = Vec::new();
|
||||
|
||||
self.0
|
||||
.serialize(&mut serde_json::Serializer::new(&mut result))?;
|
||||
|
||||
Ok(String::from_utf8(result)?)
|
||||
}
|
||||
|
||||
pub fn from_json(json_str: String) -> MizeResult<ItemData> {
|
||||
let mut json_deserializer = serde_json::Deserializer::from_str(json_str.as_str());
|
||||
|
||||
let value = CborValue::deserialize(&mut json_deserializer).mize_result_msg(format!(
|
||||
"could not deserialize the json string: {}",
|
||||
json_str
|
||||
))?;
|
||||
|
||||
return Ok(value.into_item_data());
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: ItemData) {
|
||||
item_data_merge(&mut self.0, &other.0);
|
||||
}
|
||||
|
||||
pub fn cbor(&self) -> &CborValue {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn from_cbor(cbor: CborValue) -> ItemData {
|
||||
ItemData(cbor)
|
||||
}
|
||||
|
||||
pub fn sort_keys(&mut self) -> MizeResult<()> {
|
||||
item_data_sort_keys(&mut self.0)
|
||||
}
|
||||
|
||||
pub fn null() -> CborValue {
|
||||
CborValue::Null
|
||||
}
|
||||
|
||||
pub fn parse<T: Into<String>>(value: T) -> ItemData {
|
||||
let value_str = value.into();
|
||||
if value_str == "false" {
|
||||
return CborValue::Bool(false).into_item_data();
|
||||
}
|
||||
if value_str == "false" {
|
||||
return CborValue::Bool(true).into_item_data();
|
||||
}
|
||||
if let Ok(int) = value_str.parse::<i128>() {
|
||||
return int.into_item_data();
|
||||
}
|
||||
return CborValue::Text(value_str).into_item_data();
|
||||
}
|
||||
|
||||
pub fn set_path<P: IntoPath, D: IntoItemData>(&mut self, path: P, value: D) -> MizeResult<()> {
|
||||
//trace!("[ {} ] ItemData::set_path()", "CALL".yellow());
|
||||
let path = path.into_path();
|
||||
let value = value.into_item_data();
|
||||
|
||||
item_data_set_path(&mut self.0, path, &value.0)
|
||||
}
|
||||
|
||||
pub fn get_path<P: IntoPath>(&self, path: P) -> MizeResult<ItemData> {
|
||||
let path = path.into_path();
|
||||
Ok(item_data_get_path(&self.0, path)?
|
||||
.to_owned()
|
||||
.into_item_data())
|
||||
}
|
||||
|
||||
pub fn get_paths_recursive(&mut self) -> MizeResult<Vec<String>> {
|
||||
let mut paths = Vec::new();
|
||||
let mut path_accu = Vec::new();
|
||||
get_paths(&mut self.0, &mut paths, &mut path_accu, true)?;
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
pub fn get_paths(&mut self) -> MizeResult<Vec<String>> {
|
||||
let mut paths = Vec::new();
|
||||
let mut path_accu = Vec::new();
|
||||
get_paths(&mut self.0, &mut paths, &mut path_accu, false)?;
|
||||
Ok(paths)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_paths(
|
||||
val: &mut CborValue,
|
||||
paths: &mut Vec<String>,
|
||||
path_accu: &mut Vec<String>,
|
||||
recurse: bool,
|
||||
) -> MizeResult<()> {
|
||||
let map = val
|
||||
.as_map_mut()
|
||||
.ok_or(mize_err!("ItemData is not map... when calling get_paths"))?;
|
||||
for (key, val) in map {
|
||||
let text = key
|
||||
.as_text()
|
||||
.ok_or(mize_err!("ItemData::get_paths but key is not of type text"))?
|
||||
.to_owned();
|
||||
if recurse && val.is_map() {
|
||||
let mut path_accu = path_accu.clone();
|
||||
path_accu.push(text);
|
||||
get_paths(val, paths, &mut path_accu, recurse)?;
|
||||
} else {
|
||||
let path = path_accu.join("/") + "/" + text.as_str();
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Default for ItemData {
|
||||
fn default() -> Self {
|
||||
ItemData::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data_from_string(data_string: String) -> MizeResult<ItemData> {
|
||||
let mut data = ItemData::new();
|
||||
|
||||
// in case it's just a string
|
||||
if !data_string.contains("=") {
|
||||
let cbor = CborValue::Text(data_string);
|
||||
return Ok(ItemData::from_cbor(cbor));
|
||||
}
|
||||
|
||||
for option in data_string.split(":") {
|
||||
//trace!("data_from_string: option: {}", option);
|
||||
if option == "".to_owned() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = option
|
||||
.split("=")
|
||||
.nth(0)
|
||||
.ok_or(MizeError::new().msg(format!(
|
||||
"Failed to parse Option: option '{}' has an empty path (thing before =)",
|
||||
option
|
||||
)))?;
|
||||
|
||||
//trace!("data_from_string: path: {}", path);
|
||||
|
||||
let value = option
|
||||
.split("=")
|
||||
.nth(1)
|
||||
.ok_or(MizeError::new().msg(format!(
|
||||
"Failed to parse Option: option '{}' has an empty value (thing after =)",
|
||||
option
|
||||
)))?;
|
||||
//trace!("data_from_string: value: {}", value);
|
||||
|
||||
let mut path_vec = Vec::new();
|
||||
path_vec.extend(path.split("."));
|
||||
|
||||
//trace!("data_from_string: path_vec: {:?}", path_vec);
|
||||
|
||||
data.set_path(path_vec, ItemData::parse(value))?;
|
||||
}
|
||||
|
||||
trace!("data_from_string: data: {}", data);
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
pub fn item_data_get_path(data: &CborValue, path: Vec<String>) -> MizeResult<&CborValue> {
|
||||
let mut path_iter = path.clone().into_iter();
|
||||
//println!("data: {:?}", data);
|
||||
//println!("path: {:?}", path);
|
||||
|
||||
let path_el = match path_iter.nth(0) {
|
||||
Some(val) => val,
|
||||
None => return Ok(data), // our base case
|
||||
};
|
||||
|
||||
let mut sub_data = &CborValue::Null;
|
||||
match data {
|
||||
CborValue::Map(ref map) => {
|
||||
for (key, val) in map {
|
||||
//println!("key: {:?}", key);
|
||||
//println!("path_el: {}", path_el);
|
||||
if let CborValue::Text(key_str) = key {
|
||||
if key_str == &path_el {
|
||||
sub_data = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val => {
|
||||
return Err(MizeError::new()
|
||||
.msg(format!(
|
||||
"Failed to get path '{:?}' from ItemData, the data at '{}' is not a map",
|
||||
path, path_el
|
||||
))
|
||||
.msg(format!("{:?} is: {:?}", path_el, val)));
|
||||
}
|
||||
};
|
||||
item_data_get_path(sub_data, path_iter.collect())
|
||||
}
|
||||
|
||||
pub fn item_data_sort_keys(data: &mut CborValue) -> MizeResult<()> {
|
||||
// for a list, sort each item of the list
|
||||
if data.is_array() {
|
||||
for el in data.as_array_mut().unwrap().iter_mut() {
|
||||
item_data_sort_keys(el)?
|
||||
}
|
||||
}
|
||||
|
||||
// sort the keys of a map
|
||||
if data.is_map() {
|
||||
let map: &mut Vec<(CborValue, CborValue)> = data.as_map_mut().unwrap();
|
||||
//map.sort_by_key(|el| el.0);
|
||||
map.sort_by(|a, b| {
|
||||
match a.0.partial_cmp(&b.0) {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
// just say the two values are equal, if they can't be ordered
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// also sort every sub value
|
||||
for (key, val) in map {
|
||||
item_data_sort_keys(val)?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn item_data_set_path(
|
||||
data: &mut CborValue,
|
||||
path: Vec<String>,
|
||||
data_to_set: &CborValue,
|
||||
) -> MizeResult<()> {
|
||||
//trace!("[ {} ] item_data_set_path()", "CALL".yellow());
|
||||
//trace!("[ {} ] data: {}", "ARG".yellow(), data.clone().into_item_data());
|
||||
//trace!("[ {} ] path: {:?}", "ARG".yellow(), path);
|
||||
//trace!("[ {} ] data_to_set: {}", "ARG".yellow(), data_to_set.clone().into_item_data());
|
||||
|
||||
test_println!("item_data_set_path ###########################");
|
||||
test_println!("data: {}", ItemData::from_cbor(data.clone()));
|
||||
test_println!("path: {:?}", path);
|
||||
test_println!("data_to_set: {}", ItemData::from_cbor(data_to_set.clone()));
|
||||
test_println!("###########################");
|
||||
|
||||
let mut path_iter = path.clone().into_iter();
|
||||
let path_el = match path_iter.nth(0) {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
//trace!("item_data_set_path: base_case");
|
||||
// our base case
|
||||
*data = data_to_set.to_owned();
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
match data {
|
||||
CborValue::Map(ref mut map) => {
|
||||
for (key, val) in &mut *map {
|
||||
if let CborValue::Text(key_str) = key {
|
||||
if key_str == &path_el {
|
||||
//trace!("item_data_set_path: setting key {key_str} in map with recursive call");
|
||||
return item_data_set_path(val, path_iter.collect(), data_to_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, that means, the key (which is the variable path_el) does not exist
|
||||
// in the map
|
||||
// so add it... but also with a recursive call which is needed, in case path_iter
|
||||
// longer than 1
|
||||
//trace!("item_data_set_path: adding key {path_el} to map");
|
||||
let mut inner_value = CborValue::Null;
|
||||
item_data_set_path(&mut inner_value, path_iter.collect(), data_to_set);
|
||||
map.push((CborValue::Text(path_el.clone()), inner_value));
|
||||
return Ok(());
|
||||
}
|
||||
CborValue::Null => {
|
||||
//trace!("item_data_set_path: CborValue::Null case");
|
||||
let mut inner_value = CborValue::Null;
|
||||
item_data_set_path(&mut inner_value, path_iter.collect(), data_to_set);
|
||||
|
||||
let map_vec = vec![(CborValue::Text(path_el), inner_value)];
|
||||
*data = CborValue::Map(map_vec);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
val => {
|
||||
return Err(MizeError::new()
|
||||
.msg(format!(
|
||||
"Failed to get path '{:?}' from ItemData, the data at '{}' is not a map",
|
||||
path, path_el
|
||||
))
|
||||
.msg(format!("{:?} is: {:?}", path_el, val)));
|
||||
}
|
||||
};
|
||||
|
||||
return Err(MizeError::new().msg("unreachable"));
|
||||
}
|
||||
|
||||
pub fn item_data_merge(merge_into: &mut CborValue, other: &CborValue) {
|
||||
// needs to be recursive
|
||||
|
||||
match (merge_into, other) {
|
||||
// if both are maps
|
||||
// merge the keys/vals in the maps and call recursively on them
|
||||
(CborValue::Map(ref mut merge_into_map), CborValue::Map(ref other_map)) => {
|
||||
// go through other_map and merge those keys/values to merge_into
|
||||
for (other_key, other_val) in other_map {
|
||||
// so find that same vale in merge_into, if exists recursively merge, else just add to
|
||||
// vec
|
||||
let mut found_value = CborValue::Null;
|
||||
let mut value_found = false;
|
||||
let mut new_value_for_merge_into: Vec<(CborValue, CborValue)> = Vec::new(); // here we collect the
|
||||
// new contents of the
|
||||
// map, to assign later
|
||||
// to merge_into_map
|
||||
|
||||
for (merge_into_key, merge_into_val) in merge_into_map.clone() {
|
||||
if merge_into_key == *other_key {
|
||||
value_found = true;
|
||||
found_value = merge_into_val.to_owned();
|
||||
// if we found it, also don't add it to new_value_for_merge_into
|
||||
} else {
|
||||
//if not found push to new_value_for_merge_into
|
||||
new_value_for_merge_into
|
||||
.push((merge_into_key.to_owned(), merge_into_val.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
if value_found {
|
||||
// we found it so merge and asign
|
||||
item_data_merge(&mut found_value, other_val);
|
||||
|
||||
// to asign push to new_value_for_merge_into, because the old was removed already
|
||||
new_value_for_merge_into.push((other_key.to_owned(), found_value));
|
||||
} else {
|
||||
// if not found, just add the other_key/val to new_value_for_merge_into
|
||||
new_value_for_merge_into.push((other_key.to_owned(), other_val.to_owned()));
|
||||
}
|
||||
|
||||
// finally, we need to asign the new_value_for_merge_into into merge_into
|
||||
*merge_into_map = new_value_for_merge_into;
|
||||
}
|
||||
}
|
||||
|
||||
// if other is Null, don't asign
|
||||
// this is gonna have repercussions.... because it's not clean behaviour
|
||||
(merge_into, CborValue::Null) => {}
|
||||
|
||||
// in any other case we just want to set merge_into to other
|
||||
// also this is the base case
|
||||
(merge_into, other) => {
|
||||
*merge_into = other.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(name = "fn.get_raw_from_cbor")]
|
||||
pub fn get_raw_from_cbor<'a>(value: &'a CborValue, path: Vec<&String>) -> MizeResult<&'a [u8]> {
|
||||
trace!("[ {} ] value: {:?}", "ARG".yellow(), value);
|
||||
trace!("[ {} ] path: {:?}", "ARG".yellow(), path);
|
||||
|
||||
let mut path_iter = path.clone().into_iter();
|
||||
let path_el = match path_iter.nth(0) {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
// our base case
|
||||
let ret_val = match value {
|
||||
CborValue::Bytes(vec) => {
|
||||
trace!("[ {} ] ret value: {:?}", "RET".yellow(), &vec[..]);
|
||||
return Ok(&vec[..]);
|
||||
}
|
||||
CborValue::Text(string) => {
|
||||
trace!("[ {} ] ret value: {:?}", "RET".yellow(), string.as_bytes());
|
||||
return Ok(string.as_bytes());
|
||||
}
|
||||
other => {
|
||||
return Err(mize_err!(
|
||||
"path is empty and the value: '{:?}' is neither Bytes nor Text",
|
||||
other
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
match value {
|
||||
CborValue::Bytes(vec) => {
|
||||
trace!("[ {} ] ret value: {:?}", "RET".yellow(), &vec[..]);
|
||||
return Ok(&vec[..]);
|
||||
}
|
||||
CborValue::Text(string) => {
|
||||
trace!("[ {} ] ret value: {:?}", "RET".yellow(), string.as_bytes());
|
||||
return Ok(string.as_bytes());
|
||||
}
|
||||
CborValue::Map(map) => {
|
||||
let mut inner_val: &CborValue = &CborValue::Null;
|
||||
let mut inner_val_found = false;
|
||||
|
||||
// find the inner_val at path
|
||||
for (key, val) in map.into_iter() {
|
||||
if let CborValue::Text(key_text) = key {
|
||||
if key_text == path_el {
|
||||
inner_val = val;
|
||||
inner_val_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there is no value at that path
|
||||
if inner_val_found == false {
|
||||
return Err(MizeError::new()
|
||||
// jesus christ, this is a pfusch...
|
||||
.msg(format!(
|
||||
"get_raw_from_cbor: Path '{}' not Found",
|
||||
path.clone()
|
||||
.into_iter()
|
||||
.map(|v| v.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join("/")
|
||||
)));
|
||||
}
|
||||
|
||||
//path_iter.next();
|
||||
let inner_path = path_iter.collect();
|
||||
let ret_value = get_raw_from_cbor(inner_val, inner_path);
|
||||
trace!("[ {} ] ret value: {:?}", "RET".yellow(), ret_value);
|
||||
return ret_value;
|
||||
}
|
||||
_ => Err(MizeError::new().msg(format!(
|
||||
"get_raw_from_cbor: value is not a map, text or bytes ... value: {:?}",
|
||||
value
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
trait IntoPath {
|
||||
fn into_path(self) -> Vec<String>;
|
||||
}
|
||||
|
||||
pub trait IntoItemData {
|
||||
fn into_item_data(self) -> ItemData;
|
||||
}
|
||||
|
||||
impl IntoPath for &str {
|
||||
fn into_path(self) -> Vec<String> {
|
||||
self.to_owned().split("/").map(|v| v.to_owned()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPath for &[&str] {
|
||||
fn into_path(self) -> Vec<String> {
|
||||
self.into_iter().map(|v| (*v).to_owned()).collect()
|
||||
}
|
||||
}
|
||||
impl IntoPath for Vec<String> {
|
||||
fn into_path(self) -> Vec<String> {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl IntoPath for Vec<&str> {
|
||||
fn into_path(self) -> Vec<String> {
|
||||
self.into_iter().map(|v| v.to_owned()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoItemData for &str {
|
||||
fn into_item_data(self) -> ItemData {
|
||||
ItemData::parse(self)
|
||||
}
|
||||
}
|
||||
impl IntoItemData for String {
|
||||
fn into_item_data(self) -> ItemData {
|
||||
ItemData(self.into())
|
||||
}
|
||||
}
|
||||
impl IntoItemData for &String {
|
||||
fn into_item_data(self) -> ItemData {
|
||||
ItemData(self.to_owned().into())
|
||||
}
|
||||
}
|
||||
impl IntoItemData for CborValue {
|
||||
fn into_item_data(self) -> ItemData {
|
||||
ItemData(self)
|
||||
}
|
||||
}
|
||||
impl IntoItemData for ItemData {
|
||||
fn into_item_data(self) -> ItemData {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl IntoItemData for i128 {
|
||||
fn into_item_data(self) -> ItemData {
|
||||
ItemData(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ItemData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ItemData: ");
|
||||
let display_writer = DisplayWriter(f);
|
||||
self.0
|
||||
.serialize(&mut serde_json::Serializer::pretty(display_writer))
|
||||
.map_err(|serde_err| std::fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
// thanks to: https://stackoverflow.com/a/61768916
|
||||
struct DisplayWriter<'a, 'b>(&'a mut fmt::Formatter<'b>);
|
||||
|
||||
impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> {
|
||||
fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
|
||||
self.0
|
||||
.write_str(&String::from_utf8_lossy(bytes))
|
||||
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
|
||||
|
||||
Ok(bytes.len())
|
||||
}
|
||||
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ItemData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let mut buf_other: Vec<u8> = Vec::new();
|
||||
|
||||
ciborium::into_writer(&self.0, &mut buf);
|
||||
ciborium::into_writer(&other.0, &mut buf_other);
|
||||
|
||||
buf == buf_other
|
||||
}
|
||||
}
|
||||
impl Eq for ItemData {}
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
#![ allow( warnings ) ]
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// console_log macro
|
||||
// that can be copied into other files for debugging purposes
|
||||
#[cfg(feature = "wasm-target")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "wasm-target")]
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm-target")]
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string())})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! add_parts {
|
||||
($mize:expr, $($part:expr),+) => {
|
||||
$(
|
||||
let part = $part.create($mize.clone());
|
||||
$mize.add_part(
|
||||
part
|
||||
);
|
||||
)+
|
||||
mize.init_parts();
|
||||
};
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
#![allow(warnings)]
|
||||
|
||||
static PROTO_VERSION: u8 = 1;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_println {
|
||||
($($arg:tt)*) => {
|
||||
|
||||
#[cfg(test)]
|
||||
print!("[[ {} ]] ", <&str as colored::Colorize>::blue("TEST"));
|
||||
|
||||
#[cfg(test)]
|
||||
print!($($arg)*);
|
||||
|
||||
#[cfg(test)]
|
||||
print!("\n");
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_print {
|
||||
($($arg:tt)*) => {
|
||||
|
||||
#[cfg(test)]
|
||||
print!("[[ {} ]] ", <&str as colored::Colorize>::blue("TEST"));
|
||||
|
||||
#[cfg(test)]
|
||||
print!($($arg)*);
|
||||
};
|
||||
}
|
||||
|
||||
// the core part has the code that can run on any platform
|
||||
mod core {
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod id;
|
||||
pub mod instance;
|
||||
pub mod item;
|
||||
pub mod macros;
|
||||
pub mod memstore;
|
||||
pub mod proto;
|
||||
pub mod types;
|
||||
}
|
||||
|
||||
pub use async_trait::async_trait;
|
||||
pub use core::error::MizeError;
|
||||
pub use core::error::MizeResult;
|
||||
pub use core::instance::module::Module;
|
||||
pub use core::instance::Mize;
|
||||
pub use core::instance::{
|
||||
DynMizePartGuard, MizePart, MizePartCreate, MizePartCreateGenerated, MizePartGenerated,
|
||||
MizePartGuard,
|
||||
};
|
||||
pub use core::*;
|
||||
pub use mize_macros::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// platform specific stuff
|
||||
pub mod platform {
|
||||
#[cfg(feature = "target-js")]
|
||||
pub mod js;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod os;
|
||||
|
||||
pub mod any {
|
||||
//////////// instance_init
|
||||
#[cfg(feature = "target-js")]
|
||||
pub use super::js::wasm_instance_init as instance_init;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use super::os::os_instance_init as instance_init;
|
||||
|
||||
#[cfg(not(any(feature = "target-os", feature = "target-js")))]
|
||||
pub use super::super::instance_init;
|
||||
|
||||
//////////// load_module
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use super::os::load_module;
|
||||
|
||||
#[cfg(feature = "target-js")]
|
||||
pub use super::js::load_module;
|
||||
|
||||
#[cfg(not(any(feature = "target-os", feature = "target-js")))]
|
||||
pub use super::super::load_module;
|
||||
|
||||
//////////// fetch_module
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use super::os::fetch_module;
|
||||
|
||||
#[cfg(feature = "target-js")]
|
||||
pub use super::super::fetch_module;
|
||||
|
||||
#[cfg(not(any(feature = "target-os", feature = "target-js")))]
|
||||
pub use super::super::fetch_module;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance_init(instance: &mut core::instance::Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_module(
|
||||
instance: &mut core::instance::Mize,
|
||||
name: &str,
|
||||
path: Option<String>,
|
||||
) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fetch_module(instance: &mut core::instance::Mize, name: &str) -> MizeResult<String> {
|
||||
Ok("oh noooooooooooooo, something went really really wrong, if this ends up in the executable....".to_owned())
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
use core::result::Result::Err;
|
||||
use core::unimplemented;
|
||||
use flume::bounded;
|
||||
use home::home_dir;
|
||||
use mize::mize_err;
|
||||
use std::ffi::OsString;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tracing::debug;
|
||||
|
||||
use mize::error::{IntoMizeResult, MizeError, MizeResult, MizeResultTrait};
|
||||
use mize::instance::subscription::Subscription;
|
||||
use mize::instance::subscription::Update;
|
||||
use mize::instance::Mize;
|
||||
use mize::item::{IntoItemData, ItemData};
|
||||
use mize::platform::os::config_from_cli_args;
|
||||
use mize::platform::os::fsstore::FileStore;
|
||||
|
||||
pub fn get(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
let id = sub_matches
|
||||
.get_one::<String>("id")
|
||||
.ok_or(MizeError::new().msg("No id Argument specified"))?;
|
||||
|
||||
let item = instance.get(id)?;
|
||||
|
||||
let recurse_count = sub_matches.get_count("recurse");
|
||||
|
||||
println!("{}", item.as_data_full()?);
|
||||
return Ok(());
|
||||
|
||||
if recurse_count > 0 {
|
||||
println!("{}", item.id());
|
||||
println!("{}", item.as_data_full()?);
|
||||
} else {
|
||||
println!("{}", item);
|
||||
};
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn call(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let mut instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
let item = instance.new_item()?;
|
||||
|
||||
println!("id: {}", item.id());
|
||||
println!("with namespace: {}", item.id().namespace().as_real_string());
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn is_running(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
println!("not implemented");
|
||||
Err(mize_err!("not implemented"))
|
||||
}
|
||||
|
||||
pub fn mount(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
instance.wait();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn set(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
let id = sub_matches
|
||||
.get_one::<String>("id")
|
||||
.ok_or(MizeError::new().msg("No id Argument specified"))?;
|
||||
|
||||
let value = sub_matches
|
||||
.get_one::<String>("value")
|
||||
.ok_or(MizeError::new().msg("No value Argument specified"))?;
|
||||
|
||||
instance.set_blocking(id, value.into_item_data())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
let id = sub_matches
|
||||
.get_one::<String>("id")
|
||||
.ok_or(MizeError::new().msg("No id Argument specified"))?;
|
||||
|
||||
let item = instance.get(id)?;
|
||||
|
||||
let (tx, rx) = bounded::<Update>(4);
|
||||
let sub = Subscription::from_sender(tx);
|
||||
instance.sub(id, sub)?;
|
||||
|
||||
println!("item: {}", item.as_data_full()?);
|
||||
|
||||
for update in rx {
|
||||
println!("####### GOT UPDATE #######");
|
||||
println!("item: {}", update.new_item()?.as_data_full()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gui(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let mut instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
instance.load_module("mme")?;
|
||||
|
||||
instance.wait();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn format_cbor(sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let mut input: Vec<u8> = Vec::new();
|
||||
let stdin = std::io::stdin();
|
||||
let mut handle = stdin.lock();
|
||||
//handle.read_to_end(&mut input);
|
||||
|
||||
let cbor_data = ciborium::from_reader(handle)?;
|
||||
let item_data = ItemData::from_cbor(cbor_data);
|
||||
|
||||
println!("{}", item_data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_from_module(cmd: &str, sub_matches: &ArgMatches) -> MizeResult<()> {
|
||||
let default = OsString::default();
|
||||
let mut sub_cmd_line: Vec<OsString> = sub_matches
|
||||
.get_many::<OsString>("")
|
||||
.unwrap()
|
||||
.map(|e| e.to_owned())
|
||||
.collect();
|
||||
sub_cmd_line.insert(0, OsString::from_str(cmd)?);
|
||||
|
||||
let mut instance = Mize::with_config(config_from_cli_args(sub_matches)?)?;
|
||||
|
||||
debug!("loading module '{}'", cmd);
|
||||
instance.load_module(cmd)?;
|
||||
|
||||
instance
|
||||
.get_module(cmd)?
|
||||
.run_cli(&instance, sub_cmd_line)
|
||||
.ok_or(mize_err!(
|
||||
"The Module '{cmd}' does not implement the run_cli() functionality"
|
||||
))??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,500 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use clap::{ArgAction, ArgMatches};
|
||||
use clap::{Arg, crate_version, Command};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
use colored::Colorize;
|
||||
use std::env;
|
||||
use tracing::{span_enabled, Instrument, Level};
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::filter::EnvFilter;
|
||||
use tracing_subscriber::fmt::FormatEvent;
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
use tracing_subscriber::fmt::FormatFields;
|
||||
use tracing_subscriber::fmt::FmtContext;
|
||||
use tracing_subscriber::fmt::format;
|
||||
use tracing::Event;
|
||||
use core::fmt;
|
||||
use tracing_subscriber::fmt::FormattedFields;
|
||||
use tracing::{trace, debug, info, warn, error};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use tracing::{field::Visit, Id, Subscriber};
|
||||
use tracing_core::Field;
|
||||
use std::fmt::Write;
|
||||
|
||||
use crate::MizeError;
|
||||
use crate::MizeResult;
|
||||
use crate::id::MizeId;
|
||||
use crate::item::ItemData;
|
||||
|
||||
static DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::WARN;
|
||||
|
||||
pub fn init_logger(cli_matches: &ArgMatches) {
|
||||
|
||||
// to save log messages, before the logger is setup
|
||||
let mut log_messages: Vec<(Level, String)> = Vec::new();
|
||||
|
||||
log_messages.push((Level::INFO, "Starting Mize".to_owned()));
|
||||
|
||||
// builder with DEFAULT_LOG_LEVEL
|
||||
//let mut builder = tracing_subscriber::fmt::Subscriber::builder();
|
||||
|
||||
// set loglevel
|
||||
let mut log_level = DEFAULT_LOG_LEVEL;
|
||||
log_messages.push((Level::TRACE, format!("set log level from DEFAULT_LOG_LEVEL to {}", DEFAULT_LOG_LEVEL)));
|
||||
|
||||
// check env RUST_LOG
|
||||
let mut val_rust_log = level_from_env("RUST_LOG", log_level);
|
||||
if let Some(level_rust_log) = val_rust_log.0 {
|
||||
log_level = level_rust_log;
|
||||
log_messages.push((Level::TRACE, format!("set log level from the Variable RUST_LOG to {}", level_rust_log)));
|
||||
}
|
||||
log_messages.append(&mut val_rust_log.1);
|
||||
|
||||
// check env MIZE_LOG
|
||||
let mut val_mize_log = level_from_env("MIZE_LOG", log_level);
|
||||
if let Some(level_mize_log) = val_mize_log.0 {
|
||||
log_level = level_mize_log;
|
||||
log_messages.push((Level::TRACE, format!("set log level from the Variable MIZE_LOG to {}", level_mize_log)));
|
||||
}
|
||||
log_messages.append(&mut val_mize_log.1);
|
||||
|
||||
// check -vvvvv flags
|
||||
let v_flag_count = cli_matches.get_count("verbose");
|
||||
let default_log_level_as_num = match DEFAULT_LOG_LEVEL {
|
||||
LevelFilter::OFF => 0,
|
||||
LevelFilter::ERROR => 1,
|
||||
LevelFilter::WARN => 2,
|
||||
LevelFilter::INFO => 3,
|
||||
LevelFilter::DEBUG => 4,
|
||||
LevelFilter::TRACE => 5,
|
||||
};
|
||||
let new_log_level_num = default_log_level_as_num + v_flag_count;
|
||||
log_level = match new_log_level_num {
|
||||
0 => LevelFilter::OFF,
|
||||
1 => LevelFilter::ERROR,
|
||||
2 => LevelFilter::WARN,
|
||||
3 => LevelFilter::INFO,
|
||||
4 => LevelFilter::DEBUG,
|
||||
5 => LevelFilter::TRACE,
|
||||
_ => {
|
||||
log_messages.push((Level::TRACE, format!("the number of verbose (-v) flags passed ({}) exeeds the maximumg log_level", v_flag_count)));
|
||||
LevelFilter::TRACE
|
||||
},
|
||||
};
|
||||
log_messages.push((Level::TRACE, format!("set log level from the verbose flags to {}", log_level)));
|
||||
|
||||
// check --log-level arg
|
||||
if let Some(log_level_arg_string) = cli_matches.get_one::<String>("log-level") {
|
||||
let log_level_from_arg = match log_level_arg_string.to_owned().to_lowercase().as_str() {
|
||||
"none" => LevelFilter::OFF,
|
||||
"trace" => LevelFilter::TRACE,
|
||||
"debug" => LevelFilter::DEBUG,
|
||||
"info" => LevelFilter::INFO,
|
||||
"warn" => LevelFilter::WARN,
|
||||
"error" => LevelFilter::ERROR,
|
||||
_ => {
|
||||
log_messages.push((Level::WARN, format!(
|
||||
"The argument to --log-level \"{}\" is not a valid log-level. loglevel stays at: {}"
|
||||
, log_level_arg_string
|
||||
, log_level
|
||||
)));
|
||||
log_level
|
||||
}
|
||||
};
|
||||
log_level = log_level_from_arg;
|
||||
log_messages.push((Level::TRACE, format!("set log level from the --log-level arg to {}", log_level_from_arg)));
|
||||
}
|
||||
|
||||
// check --silent flag
|
||||
if cli_matches.get_flag("silent") {
|
||||
if log_level != DEFAULT_LOG_LEVEL {
|
||||
log_messages.push((Level::WARN, format!(
|
||||
"The loglevel was set by some method (it's not the DEFAULT_LOG_LEVEL) and is now overwritten by the --siltne to be OFF"
|
||||
)))
|
||||
}
|
||||
log_level = LevelFilter::OFF;
|
||||
}
|
||||
|
||||
// actually set the log_level on the builder
|
||||
log_messages.push((Level::INFO, format!("loglevel is now {}", log_level)));
|
||||
|
||||
// filter loging
|
||||
//let mut my_env_filter = tracing_subscriber::filter::EnvFilter::new("")
|
||||
//.add_directive("[get_raw_from_cbor]=debug".parse().expect("error parsing log directive"))
|
||||
//.add_directive("[MemStore::get_value_raw]=trace".parse().expect("error parsing log directive"))
|
||||
//.add_directive("trace".parse().unwrap())
|
||||
//.add_directive("[get_raw_from_cbor]=debug".parse().expect("error parsing log directive"))
|
||||
//;
|
||||
|
||||
MinimalTracer::register();
|
||||
|
||||
//builder
|
||||
//.with_env_filter(my_env_filter)
|
||||
//.without_time()
|
||||
//.event_format(MyFormatter)
|
||||
//.with_max_level(log_level)
|
||||
//.init();
|
||||
|
||||
|
||||
// write out all log messages from log_messages
|
||||
for (level, msg) in log_messages {
|
||||
match level {
|
||||
Level::ERROR => error!("{}", msg),
|
||||
Level::WARN => warn!("{}", msg),
|
||||
Level::INFO => info!("{}", msg),
|
||||
Level::DEBUG => debug!("{}", msg),
|
||||
Level::TRACE => trace!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn level_from_env(var_name: &str, log_level: LevelFilter) -> (Option<LevelFilter>, Vec<(Level, String)>) {
|
||||
let mut log_messages: Vec<(Level, String)> = Vec::new();
|
||||
let var_value = env::var(var_name);
|
||||
match var_value {
|
||||
Ok(val) => {
|
||||
// set loglevel acording to RUST_LOG
|
||||
let level = match val.to_lowercase().as_str() {
|
||||
"none" => LevelFilter::OFF,
|
||||
"trace" => LevelFilter::TRACE,
|
||||
"debug" => LevelFilter::DEBUG,
|
||||
"info" => LevelFilter::INFO,
|
||||
"warn" => LevelFilter::WARN,
|
||||
"error" => LevelFilter::ERROR,
|
||||
_ => {
|
||||
log_messages.push((Level::WARN, format!(
|
||||
"The value of Environment Variable {}: \"{}\" is not a valid log-level. loglevel stays at: {}"
|
||||
, var_name
|
||||
, val
|
||||
, log_level
|
||||
)));
|
||||
log_level
|
||||
}
|
||||
};
|
||||
return (Some(level), log_messages)
|
||||
},
|
||||
Err(env::VarError::NotPresent) => {},
|
||||
Err(env::VarError::NotUnicode(os_string)) => {
|
||||
|
||||
let os_string_as_hex : String = os_string.clone().into_encoded_bytes().iter()
|
||||
.map(|b| format!("{:02x}", b).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
log_messages.push((Level::DEBUG, format!(
|
||||
"value of Environment Variable {} was not a unicode valid string. Therefore ignoring it.
|
||||
\n value with replacement char U+FFFD: {}
|
||||
\n the WTF-8 raw bytes as hex: {}"
|
||||
, var_name
|
||||
, os_string.to_string_lossy(), os_string_as_hex
|
||||
)));
|
||||
},
|
||||
};
|
||||
return (None, log_messages);
|
||||
}
|
||||
|
||||
|
||||
struct MyFormatter;
|
||||
|
||||
impl<S, N> FormatEvent<S, N> for MyFormatter
|
||||
where
|
||||
S: tracing::Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'a> FormatFields<'a> + 'static,
|
||||
{
|
||||
fn format_event(
|
||||
&self,
|
||||
ctx: &FmtContext<'_, S, N>,
|
||||
mut writer: format::Writer<'_>,
|
||||
event: &Event<'_>,
|
||||
) -> fmt::Result {
|
||||
// Format values from the event's's metadata:
|
||||
|
||||
let metadata = event.metadata();
|
||||
|
||||
// set format
|
||||
/*
|
||||
builder.format(|buf, record| {
|
||||
match record.level() {
|
||||
}
|
||||
});
|
||||
*/
|
||||
let level_str = match metadata.level() {
|
||||
&Level::TRACE => format!("[ {} ]", "TRACE".yellow()),
|
||||
&Level::DEBUG => format!("[ {} ]", "DEBUG".green()),
|
||||
&Level::INFO => format!("[ {} ]", "INFO".blue()),
|
||||
&Level::WARN => format!("[ {} ]", "WARN".truecolor(245, 164, 66)),
|
||||
&Level::ERROR => format!("[ {} ]", "ERROR".red()),
|
||||
};
|
||||
|
||||
// with target
|
||||
// write!(&mut writer, "{} {}: ", level_str, metadata.target())?;
|
||||
|
||||
write!(&mut writer, "{} ", level_str)?;
|
||||
|
||||
// Format all the spans in the event's span context.
|
||||
if let Some(scope) = ctx.event_scope() {
|
||||
for span in scope.from_root() {
|
||||
write!(writer, "{}", span.name())?;
|
||||
|
||||
// `FormattedFields` is a formatted representation of the span's
|
||||
// fields, which is stored in its extensions by the `fmt` layer's
|
||||
// `new_span` method. The fields will have been formatted
|
||||
// by the same field formatter that's provided to the event
|
||||
// formatter in the `FmtContext`.
|
||||
let ext = span.extensions();
|
||||
let fields = &ext
|
||||
.get::<FormattedFields<N>>()
|
||||
.expect("will never be `None`");
|
||||
|
||||
// Skip formatting the fields if the span had no fields.
|
||||
if !fields.is_empty() {
|
||||
//write!(writer, "{{{}}}", fields)?;
|
||||
}
|
||||
write!(writer, ": ")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Write fields on the event
|
||||
ctx.field_format().format_fields(writer.by_ref(), event)?;
|
||||
|
||||
writeln!(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// thanks: https://stackoverflow.com/a/75546824
|
||||
pub struct StringVisitor<'a> {
|
||||
string: &'a mut String,
|
||||
}
|
||||
impl<'a> StringVisitor<'a> {
|
||||
pub(crate) fn new(string: &'a mut String) -> Self {
|
||||
StringVisitor { string }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visit for StringVisitor<'a> {
|
||||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
||||
if field.name() == "message" {
|
||||
write!(self.string, "{value:?} ").unwrap();
|
||||
} else {
|
||||
write!(self.string, "{} = {:?}; ", field.name(), value).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LogFilter {
|
||||
target: Option<String>,
|
||||
level: Option<Level>,
|
||||
}
|
||||
|
||||
pub struct MinimalTracer {
|
||||
enabled: bool,
|
||||
filters: Vec<LogFilter>,
|
||||
level: Arc<Mutex<Level>>,
|
||||
spans: Arc<Mutex<Vec<Id>>>,
|
||||
span_levels: Arc<Mutex<HashMap<Id, Level>>>,
|
||||
span_names: Arc<Mutex<HashMap<Id, String>>>,
|
||||
span_names_to_enable: Arc<Mutex<Vec<String>>>,
|
||||
level_no_span: Level,
|
||||
}
|
||||
|
||||
fn string_to_level(string: &str) -> Option<Level> {
|
||||
match string.to_lowercase().as_str() {
|
||||
"info" => Some(Level::INFO),
|
||||
"debug" => Some(Level::DEBUG),
|
||||
"warn" | "warning" => Some(Level::WARN),
|
||||
"trace" => Some(Level::TRACE),
|
||||
"error" => Some(Level::ERROR),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimalTracer {
|
||||
pub fn register() -> Result<(), tracing::subscriber::SetGlobalDefaultError> {
|
||||
let mut enabled = true;
|
||||
let mut span_names_to_enable = Vec::new();
|
||||
let mut my_level = Level::ERROR;
|
||||
|
||||
let mut filters: Vec<LogFilter> = Vec::with_capacity(10);
|
||||
if let Ok(env_value) = env::var("RUST_LOG") {
|
||||
for filter in env_value.split(',') {
|
||||
let mut target = Some(filter);
|
||||
let mut level = None;
|
||||
if let Some(equals_index) = target.unwrap().find('=') {
|
||||
let (first, second) = filter.split_at(equals_index);
|
||||
target = Some(first);
|
||||
level = string_to_level(&second[1..])
|
||||
} else {
|
||||
my_level = string_to_level(target.unwrap()).unwrap();
|
||||
}
|
||||
let target_level = string_to_level(target.unwrap());
|
||||
|
||||
//if let Some(target_level) = target_level {
|
||||
//level = Some(target_level);
|
||||
//target = None;
|
||||
//}
|
||||
|
||||
// in case target is a span name, add id to span_names_to_enable
|
||||
// for now every target is also a span_name.....
|
||||
// i would think everything, that contains a "." would be a span, and not a module
|
||||
// targeet
|
||||
if let Some(name) = target {
|
||||
span_names_to_enable.push(name.to_owned());
|
||||
}
|
||||
|
||||
filters.push(LogFilter {
|
||||
target: target.map(|v| v.to_string()),
|
||||
level,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
tracing::subscriber::set_global_default(MinimalTracer {
|
||||
enabled, filters,
|
||||
spans: Arc::new(Mutex::new(Vec::new())),
|
||||
level: Arc::new(Mutex::new(my_level)),
|
||||
span_names: Arc::new(Mutex::new(HashMap::new())),
|
||||
span_names_to_enable: Arc::new(Mutex::new(span_names_to_enable)),
|
||||
span_levels: Arc::new(Mutex::new(HashMap::new())),
|
||||
level_no_span: my_level,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
static AUTO_ID: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
impl Subscriber for MinimalTracer {
|
||||
fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
|
||||
return true;
|
||||
|
||||
if metadata.is_span() {
|
||||
return true;
|
||||
let mut span_names_to_enable = self.span_names_to_enable.lock().unwrap();
|
||||
if span_names_to_enable.contains(&metadata.name().to_owned()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if self.enabled {
|
||||
if self.filters.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut matches: bool;
|
||||
for filter in &self.filters {
|
||||
matches = true;
|
||||
if let Some(level) = filter.level {
|
||||
if metadata.level() != &level {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
if let Some(target) = &filter.target {
|
||||
if !metadata.target().starts_with(target) {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
if matches {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn new_span(&self, _span: &tracing_core::span::Attributes<'_>) -> tracing_core::span::Id {
|
||||
let mut span_names = self.span_names.lock().unwrap();
|
||||
let mut span_levels = self.span_levels.lock().unwrap();
|
||||
let mut span_names_to_enable = self.span_names_to_enable.lock().unwrap();
|
||||
|
||||
let id = Id::from_u64(AUTO_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed) as u64);
|
||||
let span_name = _span.metadata().name().to_string();
|
||||
|
||||
span_names.insert(id.clone(), span_name.clone());
|
||||
if span_names_to_enable.contains(&span_name) {
|
||||
span_levels.insert(id.clone(), Level::TRACE);
|
||||
} else {
|
||||
span_levels.insert(id.clone(), Level::ERROR);
|
||||
}
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
fn record(&self, _span: &tracing_core::span::Id, _values: &tracing_core::span::Record<'_>) {
|
||||
}
|
||||
|
||||
fn record_follows_from(
|
||||
&self,
|
||||
_span: &tracing_core::span::Id,
|
||||
_follows: &tracing_core::span::Id,
|
||||
) {
|
||||
}
|
||||
|
||||
fn event(&self, event: &tracing::Event<'_>) {
|
||||
let metadata = event.metadata();
|
||||
|
||||
let level = metadata.level();
|
||||
let my_level = self.level.lock().unwrap();
|
||||
|
||||
//println!("uuuuuuuuuuuu event name: {}", metadata.name());
|
||||
//println!("uuuuuuuuuuuu my_level: {}", my_level);
|
||||
//println!("uuuuuuuuuuuu event level: {}", level);
|
||||
|
||||
if *level > *my_level {
|
||||
//println!("uuuuuuuuuuuu comparison");
|
||||
//println!("BLOCKED EVENT")
|
||||
return;
|
||||
}
|
||||
|
||||
let target = metadata.target();
|
||||
|
||||
let mut text = String::new();
|
||||
|
||||
let mut visitor = StringVisitor::new(&mut text);
|
||||
event.record(&mut visitor);
|
||||
|
||||
let level_str = match metadata.level() {
|
||||
&Level::TRACE => format!("[ {} ]", "TRACE".yellow()),
|
||||
&Level::DEBUG => format!("[ {} ]", "DEBUG".green()),
|
||||
&Level::INFO => format!("[ {} ]", "INFO".blue()),
|
||||
&Level::WARN => format!("[ {} ]", "WARN".truecolor(245, 164, 66)),
|
||||
&Level::ERROR => format!("[ {} ]", "ERROR".red()),
|
||||
};
|
||||
|
||||
//println!("{} {:?}", "META:".red(), metadata.in_current_span());
|
||||
let mut spans = self.spans.lock().unwrap();
|
||||
let mut span_names = self.span_names.lock().unwrap();
|
||||
let span_text = spans.iter()
|
||||
.map(|id| span_names.get(id).unwrap().to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join("::");
|
||||
println!("{level_str} {span_text} {text}");
|
||||
}
|
||||
|
||||
fn enter(&self, _span: &tracing_core::span::Id) {
|
||||
//println!("eeeeeeeeeeeeeeeee enter");
|
||||
let mut spans = self.spans.lock().unwrap();
|
||||
let mut level = self.level.lock().unwrap();
|
||||
let mut span_levels = self.span_levels.lock().unwrap();
|
||||
let mut span_names = self.span_names.lock().unwrap();
|
||||
|
||||
let name = span_names.get(_span).unwrap().to_owned();
|
||||
*level = span_levels.get(_span).unwrap().to_owned();
|
||||
spans.push(_span.clone());
|
||||
//println!("my_level is now: {}", level);
|
||||
}
|
||||
|
||||
fn exit(&self, _span: &tracing_core::span::Id) {
|
||||
//println!("eeeeeeeeeeeeeeeee exit");
|
||||
|
||||
let mut level = self.level.lock().unwrap();
|
||||
*level = self.level_no_span;
|
||||
|
||||
let mut spans = self.spans.lock().unwrap();
|
||||
spans.pop();
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
#![allow(warnings)]
|
||||
|
||||
use clap::builder::OsStr;
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use clap::{ArgAction, ArgMatches};
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
|
||||
use colored::Colorize;
|
||||
use mize::error::{MizeError, MizeResult};
|
||||
use mize::item::ItemData;
|
||||
use mize::platform::os::logging::init_logger;
|
||||
use mize::{instance, Mize};
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::filter::EnvFilter;
|
||||
use tracing_subscriber::fmt::Subscriber;
|
||||
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
mod cli;
|
||||
|
||||
static APPNAME: &str = "mize";
|
||||
|
||||
fn main() {
|
||||
// welcome to the mize source code
|
||||
// this is the main entry point for platforms with an os (eg: Linux, MacOS, Windows, BSD, ...)
|
||||
// Also let's try to make the most best documented code ever. (eventually) so it's fun to work on
|
||||
|
||||
let cli_matches = cli_matches();
|
||||
|
||||
init_logger(&cli_matches);
|
||||
|
||||
// match command
|
||||
let result = match cli_matches.subcommand() {
|
||||
// mi daemon
|
||||
Some(("run", sub_matches)) => cli::run(sub_matches),
|
||||
|
||||
Some(("is-running", sub_matches)) => cli::is_running(sub_matches),
|
||||
|
||||
// mi serve
|
||||
Some(("serve", sub_matches)) => cli::stop(sub_matches),
|
||||
|
||||
// mi mount
|
||||
Some(("mount", sub_matches)) => cli::mount(sub_matches),
|
||||
|
||||
// mi get
|
||||
Some(("get", sub_matches)) => cli::get(sub_matches),
|
||||
|
||||
// mi set
|
||||
Some(("set", sub_matches)) => cli::set(sub_matches),
|
||||
|
||||
// mi show
|
||||
Some(("show", sub_matches)) => cli::show(sub_matches),
|
||||
|
||||
// mi call
|
||||
Some(("call", sub_matches)) => cli::call(sub_matches),
|
||||
|
||||
// mi create
|
||||
Some(("create", sub_matches)) => cli::create(sub_matches),
|
||||
|
||||
// mi gui
|
||||
Some(("gui", sub_matches)) => cli::gui(sub_matches),
|
||||
|
||||
// mi format-cbor
|
||||
Some(("format-cbor", sub_matches)) => cli::format_cbor(sub_matches),
|
||||
|
||||
// some unknown command passed
|
||||
Some((cmd, sub_matches)) => cli::run_from_module(cmd, sub_matches),
|
||||
|
||||
None => Err(MizeError::new()
|
||||
.msg("No subcommand was passed. use --help to list availavle comamnds.")),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
err.log();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn cli_matches() -> clap::ArgMatches {
|
||||
let main = Command::new(APPNAME)
|
||||
.version(crate_version!())
|
||||
.author("Sebastian Moser")
|
||||
.about("The MiZe Command line tool")
|
||||
.allow_external_subcommands(true)
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.long("verbose")
|
||||
.short('v')
|
||||
.action(ArgAction::Count)
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("log-level")
|
||||
.long("log-level")
|
||||
.value_name("LOGLEVEL")
|
||||
.help("set the log-level to one of OFF, ERROR, WARN, INFO, DEBUG, TRACE")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("silent")
|
||||
.long("silent")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("set the log-level to OFF")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("folder")
|
||||
.short('f')
|
||||
.long("folder")
|
||||
.help("The folder the Instance stores all it's data and the socket for connections")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("config")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.help("overwrite config options")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("config-file")
|
||||
.long("config-file")
|
||||
.help("specify a config file")
|
||||
.global(true),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("run")
|
||||
.aliases(["r"])
|
||||
.about("Run a MiZe Instance"),
|
||||
)
|
||||
.subcommand(Command::new("stop").about("Stop a MiZe Instance"))
|
||||
.subcommand(Command::new("mount").aliases(["m"]))
|
||||
.subcommand(
|
||||
Command::new("get")
|
||||
.aliases(["g"])
|
||||
.arg(Arg::new("id").help("The id or path to get"))
|
||||
.arg(Arg::new("recurse").short('r').action(ArgAction::Count)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("set")
|
||||
.aliases(["s"])
|
||||
.arg(Arg::new("id").help("The id or path to set"))
|
||||
.arg(Arg::new("value").help("The value to set the path to")),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("show")
|
||||
.aliases(["so"])
|
||||
.arg(Arg::new("id").help("The id or path to sub to and show")),
|
||||
)
|
||||
.subcommand(Command::new("call").aliases(["c"]))
|
||||
.subcommand(Command::new("create").aliases(["cr"]))
|
||||
.subcommand(Command::new("is-running").aliases(["isr"]))
|
||||
.subcommand(Command::new("gui"))
|
||||
.subcommand(Command::new("format-cbor"))
|
||||
.arg_required_else_help(true);
|
||||
|
||||
return main.get_matches();
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
use ciborium::Value as CborValue;
|
||||
use clap::ArgMatches;
|
||||
use flate2::bufread::GzDecoder;
|
||||
use serde::Deserialize;
|
||||
use sha2::{Digest, Sha256, Sha512};
|
||||
use std::env::VarError;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::copy;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tar::Archive;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult};
|
||||
use crate::id::MizeId;
|
||||
use crate::instance::module::EmptyModule;
|
||||
use crate::instance::store::Store;
|
||||
use crate::instance::Mize;
|
||||
use crate::item::{data_from_string, IntoItemData, ItemData};
|
||||
use crate::memstore::MemStore;
|
||||
use crate::{mize_err, Module};
|
||||
|
||||
use self::fsstore::FileStore;
|
||||
|
||||
pub mod fsstore;
|
||||
pub mod logging;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
mod unix_socket;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::platform::os::unix_socket::UnixListener;
|
||||
//mod web;
|
||||
|
||||
pub fn os_instance_init(instance: &mut Mize) -> MizeResult<()> {
|
||||
// this is the code, that runs to initialize an Instance on a system with an os present.
|
||||
|
||||
////// load MIZE_CONFIG_FILE as the instance, which is item 0
|
||||
match std::env::var("MIZE_CONFIG_FILE") {
|
||||
Ok(config_file_path) => {
|
||||
let config = config_from_file(config_file_path)?;
|
||||
debug!("env var MIZE_CONFIG_FILE present");
|
||||
instance.set_blocking("0", config)?;
|
||||
trace!(
|
||||
"config after MIZE_CONFIG_FILE env var: {}",
|
||||
instance.get("self/config")?
|
||||
);
|
||||
}
|
||||
Err(var_err) => match var_err {
|
||||
VarError::NotPresent => {
|
||||
debug!("env var MIZE_CONFIG_FILE NOT present");
|
||||
}
|
||||
VarError::NotUnicode(_) => {
|
||||
warn!("env var MIZE_CONFIG_FILE is not Unicode, so not reading it")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
////// load config from MIZE_CONFIG env var
|
||||
match std::env::var("MIZE_CONFIG") {
|
||||
Ok(config_string) => {
|
||||
let config = data_from_string(config_string)?;
|
||||
debug!("env var MIZE_CONFIG present");
|
||||
instance.set_blocking("self/config", config)?;
|
||||
trace!(
|
||||
"config after MIZE_CONFIG env var: {}",
|
||||
instance.get("self/config")?
|
||||
);
|
||||
}
|
||||
Err(var_err) => match var_err {
|
||||
VarError::NotPresent => {
|
||||
debug!("env var MIZE_CONFIG NOT present")
|
||||
}
|
||||
VarError::NotUnicode(_) => {
|
||||
warn!("env var MIZE_CONFIG is not Unicode, so not reading it")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
////// if a config.store_path is set, upgrade to the filestore there
|
||||
let mut test = instance.get("0")?.as_data_full()?;
|
||||
let mut store_path = match instance.get("self/config/store_path")?.value_string() {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
// the default store_path: $HOME/.mize
|
||||
let home_dir = if let Some(dir) = std::env::home_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(mize_err!(
|
||||
"could not get home_dir and store_path is not set in the config"
|
||||
));
|
||||
};
|
||||
|
||||
let store_path_buff = home_dir.join(".mize");
|
||||
let store_path = store_path_buff.display().to_string();
|
||||
|
||||
instance.set_blocking(
|
||||
"self/config/store_path",
|
||||
store_path.clone().into_item_data(),
|
||||
)?;
|
||||
|
||||
let store_path = instance.get("self/config/store_path")?.value_string()?;
|
||||
|
||||
println!("store_path: {}", store_path);
|
||||
|
||||
store_path
|
||||
}
|
||||
};
|
||||
|
||||
if FileStore::store_is_opened(store_path.to_owned(), instance)? {
|
||||
// if the store is already opened, connect to the instance, that opened it and join
|
||||
// it's namespace
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
info!("CONNECTING");
|
||||
use crate::platform::os::unix_socket;
|
||||
unix_socket::connect(instance, store_path.into())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
error!("CONNECTING... would connect, but that is not implemented on windows yet");
|
||||
}
|
||||
} else {
|
||||
// else open it ourselves
|
||||
let file_store = FileStore::new(store_path.as_str())?;
|
||||
instance.migrate_to_store(Box::new(file_store))?;
|
||||
|
||||
let path = Path::new(&store_path).to_owned();
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
instance.add_listener(UnixListener::new(path)?)?;
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
warn!("would add a Listener on a local socket, but that is not yet implemented for windows");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn seconds_since_modification(path: &Path) -> MizeResult<u64> {
|
||||
let metadata = fs::metadata(path)?;
|
||||
|
||||
let modified = metadata.modified()?;
|
||||
|
||||
let duration = modified.duration_since(UNIX_EPOCH)?;
|
||||
|
||||
Ok(duration.as_secs())
|
||||
}
|
||||
|
||||
pub fn get_module_hash(
|
||||
instance: &mut Mize,
|
||||
name: &str,
|
||||
mut options: ItemData,
|
||||
) -> MizeResult<(String, String)> {
|
||||
let mut selector_data = instance.get("self/config/selector")?.as_data_full()?;
|
||||
|
||||
// handle the case, when we want a module for not the system we are running on...
|
||||
println!("get_module_hash: name: {}", name);
|
||||
let mut name_parts = name.split(".");
|
||||
if name_parts
|
||||
.next()
|
||||
.ok_or(mize_err!("no 0th element in the modName"))?
|
||||
== "cross"
|
||||
{
|
||||
let system = name_parts
|
||||
.next()
|
||||
.ok_or(mize_err!("no 1th element in the modName"))?;
|
||||
let mod_name: String = name_parts.collect();
|
||||
println!("get_module_hash: mod_name: {}", mod_name);
|
||||
if mod_name == "" {
|
||||
return Err(mize_err!("no 2nd element in the modName"));
|
||||
}
|
||||
|
||||
selector_data.set_path("system", system)?;
|
||||
selector_data.set_path("modName", mod_name)?;
|
||||
} else {
|
||||
selector_data.set_path("modName", name)?;
|
||||
}
|
||||
|
||||
selector_data.merge(options);
|
||||
|
||||
// important to produce the same hash for the sama selector data
|
||||
selector_data.sort_keys();
|
||||
|
||||
let selector_str = selector_data.to_json()?;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.update(selector_str.clone().as_bytes());
|
||||
|
||||
let result = hasher.finalize();
|
||||
|
||||
let mut hex: String = result
|
||||
.iter()
|
||||
.map(|b| format!("{:02x}", b).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
hex.truncate(32);
|
||||
|
||||
return Ok((hex, selector_str));
|
||||
}
|
||||
|
||||
fn download_module(
|
||||
instance: &mut Mize,
|
||||
store_path: &str,
|
||||
module_url: &str,
|
||||
module_hash: &str,
|
||||
module_name: &str,
|
||||
module_selector: &str,
|
||||
) -> MizeResult<()> {
|
||||
let tmp_file_path_gz = format!("{}/{}.tar.gz", store_path, module_hash);
|
||||
|
||||
let tmp_gz_file = if !PathBuf::from(tmp_file_path_gz.clone()).exists() {
|
||||
let mut tmp_gz_file = File::create(&tmp_file_path_gz)?;
|
||||
|
||||
debug!(
|
||||
"downloading module '{}' with hash: {}",
|
||||
module_name, module_hash
|
||||
);
|
||||
|
||||
let response = http_req::request::get(
|
||||
format!(
|
||||
"http://{}/mize/dist/{}-{}.tar.gz",
|
||||
module_url, module_hash, module_name
|
||||
),
|
||||
&mut tmp_gz_file,
|
||||
)?;
|
||||
|
||||
let code = response.status_code();
|
||||
if code != 200.into() {
|
||||
return Err(mize_err!(
|
||||
"failed to get module '{}' with hash '{}' and selector: {}",
|
||||
module_name,
|
||||
module_hash,
|
||||
module_selector
|
||||
)
|
||||
.msg(format!(
|
||||
"the http request to fetch the module returned a status code of: {}",
|
||||
code
|
||||
)));
|
||||
}
|
||||
|
||||
tmp_gz_file
|
||||
} else {
|
||||
std::fs::File::open(tmp_file_path_gz.clone())?
|
||||
};
|
||||
|
||||
let tar = GzDecoder::new(std::io::BufReader::new(tmp_gz_file));
|
||||
let mut archive = Archive::new(tar);
|
||||
|
||||
//archive.set_mask(umask::Mode::parse("rwxrwxrwx")?.into());
|
||||
|
||||
let target_dir = format!("{}/modules/{}", store_path, module_hash);
|
||||
|
||||
std::fs::create_dir_all(target_dir.as_str())?;
|
||||
|
||||
archive.unpack(target_dir.as_str())?;
|
||||
|
||||
fs::remove_file(&tmp_file_path_gz)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fetch_module(instance: &mut Mize, module_name: &str) -> MizeResult<String> {
|
||||
let module_name_with_slashes = module_name.replace(".", "/");
|
||||
if let Ok(module_path) = instance
|
||||
.get(format!(
|
||||
"self/config/module_dir/{}",
|
||||
module_name_with_slashes
|
||||
))?
|
||||
.value_string()
|
||||
{
|
||||
// in case a dir is configured, which holds the modules output
|
||||
return Ok(module_path);
|
||||
} else {
|
||||
// if null, load it from the url
|
||||
let module_url = instance.get("self/config/module_url")?.value_string()?;
|
||||
|
||||
let store_path = instance.get("self/config/store_path")?.value_string()?;
|
||||
|
||||
let (module_hash, module_selector) =
|
||||
get_module_hash(instance, module_name, ItemData::new())?;
|
||||
|
||||
if !PathBuf::from(format!("{}/modules/{}", store_path, module_hash.as_str())).exists() {
|
||||
// download module if it does not exist
|
||||
download_module(
|
||||
instance,
|
||||
store_path.as_str(),
|
||||
module_url.as_str(),
|
||||
module_hash.as_str(),
|
||||
module_name,
|
||||
module_selector.as_str(),
|
||||
)?;
|
||||
}
|
||||
|
||||
return Ok(format!("{}/modules/{}", store_path, module_hash.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_module(instance: &mut Mize, module_name: &str, path: Option<String>) -> MizeResult<()> {
|
||||
let module_path = if path.is_some() {
|
||||
format!("{}/lib/libmize_module_{}.so", path.unwrap(), module_name)
|
||||
} else {
|
||||
format!(
|
||||
"{}/lib/libmize_module_{}.so",
|
||||
fetch_module(instance, module_name)?,
|
||||
module_name
|
||||
)
|
||||
};
|
||||
|
||||
let lib = unsafe { libloading::Library::new(module_path)? };
|
||||
|
||||
let func: libloading::Symbol<
|
||||
unsafe extern "C" fn(&mut Box<dyn Module + Send + Sync>, Mize) -> (),
|
||||
> = unsafe { lib.get(format!("get_mize_module_{}", module_name).as_bytes())? };
|
||||
|
||||
let mut module: Box<dyn Module + Send + Sync> = Box::new(EmptyModule {});
|
||||
|
||||
unsafe { func(&mut module, instance.clone()) };
|
||||
|
||||
let mut modules_inner = instance.modules.lock()?;
|
||||
|
||||
module.init(&instance)?;
|
||||
|
||||
modules_inner.insert(module_name.to_owned(), module);
|
||||
|
||||
unsafe {
|
||||
// dropping the lib, would (i suspect) free all memory, of the library's code, which would
|
||||
// make the module's vtable point into empty memory
|
||||
std::mem::forget(lib);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config_from_cli_args(matches: &ArgMatches) -> MizeResult<ItemData> {
|
||||
let mut config = ItemData::new();
|
||||
|
||||
if let Some(config_file_path) = matches.get_one::<String>("config-file") {
|
||||
config.merge(config_from_file(config_file_path.to_string())?);
|
||||
trace!("config after --config-file arg: {}", config);
|
||||
}
|
||||
|
||||
if let Some(config_string) = matches.get_one::<String>("config") {
|
||||
config.merge(data_from_string(config_string.to_string())?);
|
||||
trace!("config after --config arg: {}", config);
|
||||
}
|
||||
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
pub fn config_from_file(file_path: String) -> MizeResult<ItemData> {
|
||||
let toml_string = fs::read_to_string(&file_path)
|
||||
.mize_result_msg(format!("Could not read file {} as mize config", &file_path))?;
|
||||
|
||||
let item_data = ItemData::from_toml(toml_string.as_str())?;
|
||||
|
||||
return Ok(item_data);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user