mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-15 21:32:26 +01:00
feat(bar-core): per-platform views of one union bar schema
The Windows schema.bar.json is the union of the bar configuration (every widget, the mouse options, Windows spelling). New module `schema` holds the macOS view as data: 16 changes (set text/defaults that read differently, remove the mouse property, the Keyboard/Media/Systray widget variants and the NetworkConfig default_interface_options) followed by pruning of the definitions nothing refers to any more. Same design as komorebi-config (schema.rs, changes.rs); the changes are idempotent so a bar can print its own generated schema through its view. Nothing uses the views yet; no bar source changed. Verified: native `cargo test -p komorebi-bar-core` 3 unit + 5 oracle passed (new: the macOS view of the checked-in Windows file is byte-identical to komorebi-for-mac/schema.bar.json; the Windows view is the Windows file; a view is idempotent). Windows clippy -D warnings clean; macOS fake-CC check 0 errors. Not verified: nothing rendered.
This commit is contained in:
1 parent
633e7453cd
commit
280eea6fec
5 files changed
+267
-1
No files matched your search
@@ -11,7 +11,7 @@ eframe = { workspace = true }
|
||||
egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" }
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_json = { workspace = true, features = ["preserve_order"] }
|
||||
starship-battery = "0.11"
|
||||
sysinfo = { workspace = true }
|
||||
|
||||
|
||||
@@ -18,3 +18,5 @@ pub mod render;
|
||||
pub mod widgets;
|
||||
|
||||
pub mod platform;
|
||||
|
||||
pub mod schema;
|
||||
@@ -0,0 +1,99 @@
|
||||
//! What the macOS bar's schema says differently from the union, as data.
|
||||
//!
|
||||
//! The union is written once: it is the Windows text of `schema.bar.json` (every widget, the
|
||||
//! mouse options, Windows' spelling of the documentation). The macOS view removes what the macOS
|
||||
//! bar does not have (the keyboard, media and systray widgets, the mouse options and their
|
||||
//! definitions, which pruning of the unreachable definitions finishes) and sets the text and the
|
||||
//! defaults that read differently. Each entry is a convergence candidate: dropping one changes
|
||||
//! one line of `komorebi-for-mac/schema.bar.json` and of `nix/komorebi-bar-options.nix`.
|
||||
//!
|
||||
//! Generated from the difference between the two checked-in files; `tests/schema_oracle.rs`
|
||||
//! proves that the view of the Windows file is byte-identical to the macOS file.
|
||||
|
||||
/// One change of a view
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Change {
|
||||
/// Replace the value at the JSON pointer with this JSON text
|
||||
Set(&'static str),
|
||||
/// Remove the key at the JSON pointer
|
||||
Remove,
|
||||
/// Remove the elements of the array at the JSON pointer whose `title` is this
|
||||
RemoveTitled(&'static str),
|
||||
}
|
||||
|
||||
/// The changes of the macOS view, in order.
|
||||
pub const MACOS: &[(&str, Change)] = &[
|
||||
(
|
||||
"/description",
|
||||
Change::Set(
|
||||
r####""The `komorebi.bar.json` static configuration file reference for komorebi for Mac `v0.1.0`\n\n`\"$schema\": \"https://komorebi-for-mac.lgug2z.com/komorebi.bar.0.1.0.schema.json\"`""####,
|
||||
),
|
||||
),
|
||||
(
|
||||
"/properties/height",
|
||||
Change::Set(
|
||||
r####"{"description":"Bar height","type":["number","null"],"format":"float","default":50.0}"####,
|
||||
),
|
||||
),
|
||||
(
|
||||
"/properties/margin",
|
||||
Change::Set(
|
||||
r####"{"description":"Bar margin. Use one value for all sides or use a grouped margin for horizontal and/or\nvertical definition which can each take a single value for a symmetric margin or two\nvalues for each side, i.e.:\n```json\n\"margin\": {\n \"horizontal\": 10.0\n}\n```\nor:\n```json\n\"margin\": {\n \"vertical\": [top, bottom]\n}\n```\nYou can also set individual margin on each side like this:\n```json\n\"margin\": {\n \"top\": 10.0,\n \"bottom\": 10.0,\n \"left\": 10.0,\n \"right\": 10.0\n}\n```","anyOf":[{"$ref":"#/$defs/SpacingKind"},{"type":"null"}]}"####,
|
||||
),
|
||||
),
|
||||
(
|
||||
"/properties/padding",
|
||||
Change::Set(
|
||||
r####"{"description":"Bar padding. Use one value for all sides or use a grouped padding for horizontal and/or\nvertical definition which can each take a single value for a symmetric padding or two\nvalues for each side, i.e.:\n```json\n\"padding\": {\n \"horizontal\": 10.0\n}\n```\nor:\n```json\n\"padding\": {\n \"horizontal\": [left, right]\n}\n```\nYou can also set individual padding on each side like this:\n```json\n\"padding\": {\n \"top\": 10.0,\n \"bottom\": 10.0,\n \"left\": 10.0,\n \"right\": 10.0\n}\n```","anyOf":[{"$ref":"#/$defs/SpacingKind"},{"type":"null"}]}"####,
|
||||
),
|
||||
),
|
||||
("/properties/mouse", Change::Remove),
|
||||
(
|
||||
"/$defs/KomorebiFocusedContainerConfig/properties/show_icon",
|
||||
Change::Set(
|
||||
r####"{"description":"DEPRECATED: use 'display' instead (Show the icon of the currently focused container)","type":["boolean","null"]}"####,
|
||||
),
|
||||
),
|
||||
(
|
||||
"/$defs/KomorebiLayout",
|
||||
Change::Set(
|
||||
r####"{"description":"Komorebi layout kind","oneOf":[{"title":"Default","description":"Predefined layout","type":"object","properties":{"Default":{"$ref":"#/$defs/DefaultLayout"}},"additionalProperties":false,"required":["Default"]},{"description":"Monocle mode","type":"string","const":"Monocle"},{"description":"Floating layer","type":"string","const":"Floating"},{"description":"Paused","type":"string","const":"Paused"},{"description":"Custom layout","type":"string","const":"Custom"}]}"####,
|
||||
),
|
||||
),
|
||||
(
|
||||
"/$defs/NetworkConfig/properties/default_interface_options",
|
||||
Change::Remove,
|
||||
),
|
||||
(
|
||||
"/$defs/StorageDisplayName/oneOf/0/description",
|
||||
Change::Set(r####""Display label as mount point eg. /""####),
|
||||
),
|
||||
(
|
||||
"/$defs/StorageDisplayName/oneOf/1/description",
|
||||
Change::Set(r####""Display label as name eg. Macintosh HD""####),
|
||||
),
|
||||
(
|
||||
"/$defs/StorageDisplayName/oneOf/2/description",
|
||||
Change::Set(r####""Display label as mount then name eg. / Macintosh HD""####),
|
||||
),
|
||||
(
|
||||
"/$defs/StorageDisplayName/oneOf/3/description",
|
||||
Change::Set(r####""Display label as name then mount eg. Macintosh HD /""####),
|
||||
),
|
||||
(
|
||||
"/$defs/TimeConfig/properties/changing_icon",
|
||||
Change::Set(
|
||||
r####"{"description":"Change the icon depending on the time. The default icon is used between 8:30 and 12:00","type":["boolean","null"],"default":false}"####,
|
||||
),
|
||||
),
|
||||
(
|
||||
"/$defs/TimeFormat/oneOf/6/title",
|
||||
Change::Set(r####""Default""####),
|
||||
),
|
||||
(
|
||||
"/$defs/WidgetConfig/oneOf",
|
||||
Change::RemoveTitled("Keyboard"),
|
||||
),
|
||||
("/$defs/WidgetConfig/oneOf", Change::RemoveTitled("Media")),
|
||||
("/$defs/WidgetConfig/oneOf", Change::RemoveTitled("Systray")),
|
||||
];
|
||||
@@ -0,0 +1,120 @@
|
||||
//! The JSON Schema of `komorebi.bar.json`, one per platform, as views of one union.
|
||||
//!
|
||||
//! The union is the Windows schema: every widget and option any bar reads, in Windows' spelling.
|
||||
//! [`view_of`] turns it into the schema a platform checks in: it applies the platform's
|
||||
//! [`changes`] and removes the definitions nothing refers to any more. The Windows view is the
|
||||
//! union itself.
|
||||
//!
|
||||
//! The changes are idempotent (setting text that is already there, or removing what is not
|
||||
//! there, does nothing), so a bar can print its own generated schema through its view.
|
||||
|
||||
pub mod changes;
|
||||
|
||||
use changes::Change;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
|
||||
/// A platform that has a bar
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SchemaPlatform {
|
||||
Windows,
|
||||
MacOs,
|
||||
}
|
||||
|
||||
/// The changes a platform's view applies, in order
|
||||
#[must_use]
|
||||
pub const fn changes_of(platform: SchemaPlatform) -> &'static [(&'static str, Change)] {
|
||||
match platform {
|
||||
SchemaPlatform::Windows => &[],
|
||||
SchemaPlatform::MacOs => changes::MACOS,
|
||||
}
|
||||
}
|
||||
|
||||
/// The text a bar prints for `--schema`: pretty JSON followed by a newline, of the view of the
|
||||
/// union of the bar configuration.
|
||||
#[must_use]
|
||||
pub fn view_text(union: Value, platform: SchemaPlatform) -> String {
|
||||
let mut text =
|
||||
serde_json::to_string_pretty(&view_of(union, platform)).expect("a schema is JSON");
|
||||
text.push('\n');
|
||||
text
|
||||
}
|
||||
|
||||
/// The view of `schema` for `platform`.
|
||||
#[must_use]
|
||||
pub fn view_of(mut schema: Value, platform: SchemaPlatform) -> Value {
|
||||
for (pointer, change) in changes_of(platform) {
|
||||
apply(&mut schema, pointer, *change);
|
||||
}
|
||||
prune_unreachable(&mut schema);
|
||||
schema
|
||||
}
|
||||
|
||||
fn apply(schema: &mut Value, pointer: &str, change: Change) {
|
||||
match change {
|
||||
Change::Set(json) => {
|
||||
if let Some(target) = schema.pointer_mut(pointer) {
|
||||
*target = serde_json::from_str(json).expect("a change is JSON");
|
||||
}
|
||||
}
|
||||
Change::Remove => {
|
||||
if let Some((parent, key)) = pointer.rsplit_once('/')
|
||||
&& let Some(Value::Object(map)) = schema.pointer_mut(parent)
|
||||
{
|
||||
map.retain(|k, _| k != key);
|
||||
}
|
||||
}
|
||||
Change::RemoveTitled(title) => {
|
||||
if let Some(Value::Array(items)) = schema.pointer_mut(pointer) {
|
||||
items.retain(|item| item.get("title").and_then(Value::as_str) != Some(title));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep the definitions that the schema refers to, directly or through another definition.
|
||||
fn prune_unreachable(schema: &mut Value) {
|
||||
let mut reachable = Vec::new();
|
||||
collect_refs(schema, &mut reachable);
|
||||
let Some(defs) = schema.get("$defs").and_then(Value::as_object).cloned() else {
|
||||
return;
|
||||
};
|
||||
let mut next = 0;
|
||||
while next < reachable.len() {
|
||||
if let Some(def) = defs.get(&reachable[next]) {
|
||||
let mut found = Vec::new();
|
||||
collect_refs(def, &mut found);
|
||||
for name in found {
|
||||
if !reachable.contains(&name) {
|
||||
reachable.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
next += 1;
|
||||
}
|
||||
let kept: Map<String, Value> = defs
|
||||
.into_iter()
|
||||
.filter(|(name, _)| reachable.contains(name))
|
||||
.collect();
|
||||
schema["$defs"] = Value::Object(kept);
|
||||
}
|
||||
|
||||
fn collect_refs(value: &Value, found: &mut Vec<String>) {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
if let Some(Value::String(target)) = map.get("$ref")
|
||||
&& let Some(name) = target.strip_prefix("#/$defs/")
|
||||
&& !found.iter().any(|n| n == name)
|
||||
{
|
||||
found.push(name.to_string());
|
||||
}
|
||||
for (key, child) in map {
|
||||
if key != "$defs" {
|
||||
collect_refs(child, found);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(items) => items.iter().for_each(|item| collect_refs(item, found)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -76,3 +76,48 @@ fn the_checked_in_bar_schemas_are_json_schemas_of_komobar_config() {
|
||||
assert!(text.ends_with("}\n"), "{}", pin.file);
|
||||
}
|
||||
}
|
||||
|
||||
mod views {
|
||||
use komorebi_bar_core::schema::SchemaPlatform;
|
||||
use komorebi_bar_core::schema::view_text;
|
||||
|
||||
fn text(file: &str) -> String {
|
||||
String::from_utf8(super::read(file)).expect("utf-8")
|
||||
}
|
||||
|
||||
/// The Windows file is the union; its Windows view is itself.
|
||||
#[test]
|
||||
fn the_windows_view_of_the_union_is_the_windows_file() {
|
||||
let union = serde_json::from_str(&text("schema.bar.json")).expect("json");
|
||||
assert!(view_text(union, SchemaPlatform::Windows) == text("schema.bar.json"));
|
||||
}
|
||||
|
||||
/// The macOS view of the union is the macOS file, byte for byte.
|
||||
#[test]
|
||||
fn the_macos_view_of_the_union_is_the_macos_file() {
|
||||
let union = serde_json::from_str(&text("schema.bar.json")).expect("json");
|
||||
let view = view_text(union, SchemaPlatform::MacOs);
|
||||
let file = text("komorebi-for-mac/schema.bar.json");
|
||||
if view != file {
|
||||
let first = view.lines().zip(file.lines()).position(|(a, b)| a != b);
|
||||
panic!(
|
||||
"the views differ, first at line {first:?} (view {} lines, file {})",
|
||||
view.lines().count(),
|
||||
file.lines().count()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A view applied to its own output changes nothing, so a bar can print its generated
|
||||
/// schema through it.
|
||||
#[test]
|
||||
fn a_view_is_idempotent() {
|
||||
let union = serde_json::from_str(&text("schema.bar.json")).expect("json");
|
||||
let once = view_text(union, SchemaPlatform::MacOs);
|
||||
let again = view_text(
|
||||
serde_json::from_str(&once).expect("json"),
|
||||
SchemaPlatform::MacOs,
|
||||
);
|
||||
assert!(once == again);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user