what did i een do
This commit is contained in:
@@ -1,173 +0,0 @@
|
|||||||
use macaddr::MacAddr6;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{net::IpAddr, process::Command};
|
|
||||||
|
|
||||||
// Raw JSON shape from ip -j -4 address show
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct IpJAddrInfo {
|
|
||||||
family: Option<String>,
|
|
||||||
local: Option<String>,
|
|
||||||
broadcast: Option<String>,
|
|
||||||
scope: Option<String>,
|
|
||||||
label: Option<String>,
|
|
||||||
prefixlen: Option<u8>,
|
|
||||||
// many more exist; we only take what we need
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct IpJAddrEntry {
|
|
||||||
ifname: String,
|
|
||||||
// interface MAC address (present on non-loopback):
|
|
||||||
address: Option<String>,
|
|
||||||
addr_info: Option<Vec<IpJAddrInfo>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct AddrInfo {
|
|
||||||
pub local: IpAddr,
|
|
||||||
pub broadcast: Option<IpAddr>,
|
|
||||||
pub scope: Option<String>,
|
|
||||||
pub label: Option<String>,
|
|
||||||
pub prefixlen: Option<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct IpAEntry {
|
|
||||||
pub ifname: String,
|
|
||||||
pub mac: Option<MacAddr6>,
|
|
||||||
pub addr_info: Vec<AddrInfo>,
|
|
||||||
// IPv4 only (inet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run ip -j -4 address show and deserialize
|
|
||||||
fn read_ip_json() -> Result<Vec<IpJAddrEntry>, Box<dyn std::error::Error>> {
|
|
||||||
let out = Command::new("ip")
|
|
||||||
.args(["-j", "-4", "address", "show"])
|
|
||||||
.output()?;
|
|
||||||
if !out.status.success() {
|
|
||||||
return Err(format!("ip exited with {}", out.status).into());
|
|
||||||
}
|
|
||||||
let entries: Vec<IpJAddrEntry> = serde_json::from_slice(&out.stdout)?;
|
|
||||||
Ok(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_ip(s: &str) -> Option<IpAddr> {
|
|
||||||
s.parse::<IpAddr>().ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_mac(s: &str) -> Option<MacAddr6> {
|
|
||||||
// ip outputs lowercase aa:bb:..., which MacAddr6 can parse
|
|
||||||
s.parse::<MacAddr6>().ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public: read and convert to a cleaned model
|
|
||||||
pub fn get_ip_addr_entries() -> Result<Vec<IpAEntry>, Box<dyn std::error::Error>> {
|
|
||||||
let raw = read_ip_json()?;
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for e in raw {
|
|
||||||
let mac = e.address.as_deref().and_then(parse_mac);
|
|
||||||
let mut infos = Vec::new();
|
|
||||||
|
|
||||||
if let Some(list) = e.addr_info {
|
|
||||||
for ai in list {
|
|
||||||
// keep only IPv4
|
|
||||||
if ai.family.as_deref() != Some("inet") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(local) = ai.local.as_deref().and_then(parse_ip) {
|
|
||||||
let broadcast = ai.broadcast.as_deref().and_then(parse_ip);
|
|
||||||
infos.push(AddrInfo {
|
|
||||||
local,
|
|
||||||
broadcast,
|
|
||||||
scope: ai.scope,
|
|
||||||
label: ai.label,
|
|
||||||
prefixlen: ai.prefixlen,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push(IpAEntry {
|
|
||||||
ifname: e.ifname,
|
|
||||||
mac,
|
|
||||||
addr_info: infos,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: first “good” (global) broadcast on an interface
|
|
||||||
pub fn broadcast_for_ifname(ifname: &str) -> Result<Option<IpAddr>, Box<dyn std::error::Error>> {
|
|
||||||
let entries = get_ip_addr_entries()?;
|
|
||||||
let dev = entries.into_iter().find(|e| e.ifname == ifname);
|
|
||||||
if let Some(dev) = dev {
|
|
||||||
// Prefer scope=global with a broadcast; else any broadcast
|
|
||||||
if let Some(b) = dev
|
|
||||||
.addr_info
|
|
||||||
.iter()
|
|
||||||
.filter(|ai| ai.scope.as_deref() == Some("global"))
|
|
||||||
.filter_map(|ai| ai.broadcast)
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
return Ok(Some(b));
|
|
||||||
}
|
|
||||||
if let Some(b) = dev.addr_info.iter().filter_map(|ai| ai.broadcast).next() {
|
|
||||||
return Ok(Some(b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: broadcast for a given interface MAC
|
|
||||||
pub fn broadcast_for_mac(
|
|
||||||
mac: MacAddr6,
|
|
||||||
) -> Result<Option<(String, IpAddr)>, Box<dyn std::error::Error>> {
|
|
||||||
let entries = get_ip_addr_entries()?;
|
|
||||||
for e in entries {
|
|
||||||
if let Some(m) = e.mac {
|
|
||||||
if m == mac {
|
|
||||||
// prefer global broadcast
|
|
||||||
if let Some(b) = e
|
|
||||||
.addr_info
|
|
||||||
.iter()
|
|
||||||
.filter(|ai| ai.scope.as_deref() == Some("global"))
|
|
||||||
.filter_map(|ai| ai.broadcast)
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
return Ok(Some((e.ifname, b)));
|
|
||||||
}
|
|
||||||
if let Some(b) = e.addr_info.iter().filter_map(|ai| ai.broadcast).next() {
|
|
||||||
return Ok(Some((e.ifname, b)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: if nothing else, use limited broadcast (may be filtered on some networks)
|
|
||||||
pub fn fallback_broadcast() -> IpAddr {
|
|
||||||
// 255.255.255.255
|
|
||||||
IpAddr::from([255, 255, 255, 255])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience: all broadcasts grouped by interface (IPv4)
|
|
||||||
pub fn all_broadcasts() -> Result<Vec<(String, IpAddr)>, Box<dyn std::error::Error>> {
|
|
||||||
let Some(r) = Some(12) else {
|
|
||||||
e.addr_info
|
|
||||||
.iter()
|
|
||||||
.filter(|ai| ai.scope.as_deref() == Some("global"))
|
|
||||||
.filter_map(|ai| ai.broadcast)
|
|
||||||
};
|
|
||||||
let entries = get_ip_addr_entries()?;
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for e in entries {
|
|
||||||
for ai in e.addr_info {
|
|
||||||
if let Some(b) = ai.broadcast {
|
|
||||||
out.push((e.ifname.clone(), b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
+118
-22
@@ -1,15 +1,20 @@
|
|||||||
"thanks chatgpt"
|
"thanks chatgpt"
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from functools import wraps
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
root = Path(__file__).parent.parent
|
root = Path(__file__).parent.parent
|
||||||
static = root / "static"
|
static = root / "static"
|
||||||
src = root / "src"
|
src = root / "src"
|
||||||
out_rs = src / "assets.rs"
|
out_rs = src / "assets.rs"
|
||||||
|
|
||||||
assert 'name = "wakey"' in (root / "Cargo.toml").read_text(
|
assert 'name = "wakey"' in (root / "Cargo.toml").read_text(encoding="utf-8"), (
|
||||||
encoding="utf-8"
|
"uhh how do i explain this"
|
||||||
), "uhh how do i explain this"
|
)
|
||||||
|
|
||||||
|
|
||||||
def sanitize(name: str) -> str:
|
def sanitize(name: str) -> str:
|
||||||
# Valid Rust identifiers: letters, digits, underscores; no starting digit
|
# Valid Rust identifiers: letters, digits, underscores; no starting digit
|
||||||
@@ -30,39 +35,130 @@ def indent(text: str, n: int) -> str:
|
|||||||
return "\n".join(pad + line if line.strip() else line for line in text.splitlines())
|
return "\n".join(pad + line if line.strip() else line for line in text.splitlines())
|
||||||
|
|
||||||
|
|
||||||
class RsAssetFile:
|
class RsAsset(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def plain(self) -> str: ...
|
||||||
|
@abstractmethod
|
||||||
|
def macroed(self) -> str: ...
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetFile(RsAsset):
|
||||||
def __init__(self, path: Path):
|
def __init__(self, path: Path):
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
def __str__(self):
|
def apply_template(self, template):
|
||||||
const_name = sanitize(self.path.name).upper()
|
return template.format(
|
||||||
return (
|
const_name=sanitize(self.path.name).upper(),
|
||||||
f"pub const {const_name}: &str = "
|
relative_path=self.path.relative_to(src, walk_up=True).as_posix(),
|
||||||
f'include_str!("{self.path.relative_to(src, walk_up=True).as_posix()}");'
|
|
||||||
)
|
)
|
||||||
# specify walk_up to have .. in yo path
|
|
||||||
|
plain_template = 'pub const {const_name}: &str = include_str!("{relative_path}");'
|
||||||
|
|
||||||
|
macroed_template = 'file {const_name} "{relative_path}"'
|
||||||
|
|
||||||
|
def plain(self):
|
||||||
|
return self.apply_template(self.plain_template)
|
||||||
|
|
||||||
|
def macroed(self):
|
||||||
|
return self.apply_template(self.macroed_template)
|
||||||
|
|
||||||
|
|
||||||
class RsAssetModule:
|
class RsAssetModule(RsAsset):
|
||||||
def __init__(self, folder: Path, body_only: bool = False):
|
def __init__(self, folder: Path, body_only: bool = False):
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
self.full = not body_only
|
self.full = not body_only
|
||||||
|
|
||||||
def __str__(self):
|
plain_template = "pub mod {sanitized_name} {{\n{indented_body}\n}}"
|
||||||
files = [str(RsAssetFile(s)) for s in self.folder.iterdir() if s.is_file()]
|
|
||||||
subs = [str(RsAssetModule(s)) for s in self.folder.iterdir() if s.is_dir()]
|
|
||||||
|
|
||||||
body = "\n".join(files + subs)
|
macroed_template = "folder {sanitized_name} {{\n{indented_body}\n}}"
|
||||||
return (
|
|
||||||
f"pub mod {sanitize(self.folder.name).lower()} {{" * self.full
|
sibling = "\n"
|
||||||
+ f"\n{indent(body, 4 * self.full)}\n"
|
|
||||||
+ self.full * "}"
|
def plain(self):
|
||||||
|
return self.apply_template(self.plain_template, lambda it: it.plain())
|
||||||
|
|
||||||
|
def macroed(self):
|
||||||
|
return self.apply_template(self.macroed_template, lambda it: it.macroed())
|
||||||
|
|
||||||
|
def apply_template(self, template: str, renderer: Callable[[RsAsset], str]):
|
||||||
|
body = self.process_body(renderer)
|
||||||
|
if not self.full:
|
||||||
|
return body
|
||||||
|
return template.format(
|
||||||
|
sanitized_name=sanitize(self.folder.name).lower(),
|
||||||
|
indented_body=indent(body, 4),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def process_body(self, renderer: Callable[[RsAsset], str]):
|
||||||
|
body = self.iterate_assets()
|
||||||
|
return self.sibling.join(map(renderer, body))
|
||||||
|
|
||||||
|
def iterate_assets(self) -> Iterable[RsAsset]:
|
||||||
|
subs = []
|
||||||
|
for f in self.folder.iterdir():
|
||||||
|
if f.is_file():
|
||||||
|
yield RsAssetFile(f)
|
||||||
|
elif f.is_dir():
|
||||||
|
subs.append(RsAssetModule(f))
|
||||||
|
yield from subs
|
||||||
|
|
||||||
|
|
||||||
|
lda_macro = """
|
||||||
|
macro_rules! hehe {
|
||||||
|
// Folder
|
||||||
|
(folder $name:ident { $($children:tt)* } $($rest:tt)*) => {
|
||||||
|
pub mod $name {
|
||||||
|
hehe!{$($children)*}
|
||||||
|
}
|
||||||
|
hehe!{$($rest)*}
|
||||||
|
};
|
||||||
|
// File
|
||||||
|
(file $name:ident $file:literal $($rest:tt)*) => {
|
||||||
|
pub const $name: &str = include_str!($file);
|
||||||
|
hehe!{$($rest)*}
|
||||||
|
};
|
||||||
|
// Base case
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
header = "// generated with ./scripts/map_static.py"
|
||||||
|
|
||||||
|
|
||||||
|
def announce_yourself(what: str):
|
||||||
|
def wpr[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||||
|
@wraps(f)
|
||||||
|
def wpd(*a: P.args, **k: P.kwargs) -> R:
|
||||||
|
print(what)
|
||||||
|
return f(*a, **k)
|
||||||
|
|
||||||
|
return wpd
|
||||||
|
|
||||||
|
return wpr
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetRoot(RsAsset):
|
||||||
|
def __init__(self, asset: Path) -> None:
|
||||||
|
self.a = RsAssetModule(asset, True)
|
||||||
|
|
||||||
|
@announce_yourself("generating code macro style")
|
||||||
|
def macroed(self):
|
||||||
|
return f"""{header}
|
||||||
|
{lda_macro}
|
||||||
|
hehe! {{
|
||||||
|
{indent(self.a.macroed(), 4)}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
@announce_yourself("generating code plain ahh style")
|
||||||
|
def plain(self) -> str:
|
||||||
|
return f"""{header}
|
||||||
|
|
||||||
|
{self.a.plain()}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
rs_code = RsAssetRoot(static).plain()
|
||||||
|
|
||||||
# generate
|
# generate
|
||||||
rs_code = "// generated with ./scripts/map_static.py\n" + str(
|
|
||||||
RsAssetModule(static, True)
|
|
||||||
)
|
|
||||||
out_rs.write_text(rs_code, encoding="utf-8")
|
out_rs.write_text(rs_code, encoding="utf-8")
|
||||||
print(f"written to {out_rs.relative_to(Path.cwd(), walk_up=True)}")
|
print(f"written to {out_rs.relative_to(Path.cwd(), walk_up=True)}")
|
||||||
|
|||||||
Reference in New Issue
Block a user