added GUI
This commit is contained in:
parent
6833152495
commit
4abbffa2bf
22
Cargo.toml
22
Cargo.toml
|
@ -1,20 +1,2 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "upgr"
|
members = ["gupgr", "libupgr", "upgr"]
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap = { version = "3.0.0-beta.5", features = ["derive"] }
|
|
||||||
dirs = "4.0.0"
|
|
||||||
miette = { version = "3.2.0", features = ["fancy"] }
|
|
||||||
mlua = { version = "0.6.6", features = ["luajit", "serialize"] }
|
|
||||||
owo-colors = "3.1.0"
|
|
||||||
serde = { version = "1.0.130", features = ["derive"] }
|
|
||||||
sled = "0.34.7"
|
|
||||||
thiserror = "1.0.30"
|
|
||||||
walkdir = "2.3.2"
|
|
||||||
xxhash-rust = { version = "0.8.2", features = ["xxh3"] }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
|
|
18
gupgr/Cargo.toml
Normal file
18
gupgr/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "gupgr"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crossbeam-channel = "0.5.2"
|
||||||
|
dirs = "4.0.0"
|
||||||
|
libupgr = { path = "../libupgr" }
|
||||||
|
miette = "3.3.0"
|
||||||
|
mlua = { version = "0.7.3", features = ["luajit"] }
|
||||||
|
relm4 = { version = "0.4.2", features = ["macros"] }
|
||||||
|
sled = "0.34.7"
|
||||||
|
vte4 = "0.1.0"
|
||||||
|
|
||||||
|
[features]
|
102
gupgr/src/lua_actor.rs
Normal file
102
gupgr/src/lua_actor.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use std::{fs, thread};
|
||||||
|
|
||||||
|
use crossbeam_channel::{bounded, Sender};
|
||||||
|
use libupgr::config::{Config, Step};
|
||||||
|
use miette::{miette, Context, IntoDiagnostic};
|
||||||
|
use mlua::Lua;
|
||||||
|
|
||||||
|
pub fn start() -> miette::Result<LuaActorHandle> {
|
||||||
|
let (lua_tx, lua_rx) = bounded(16);
|
||||||
|
let (startup_tx, statup_rx) = bounded(0);
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut actor = match LuaActor::init() {
|
||||||
|
Ok(a) => {
|
||||||
|
startup_tx.send(Ok(())).unwrap();
|
||||||
|
a
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
startup_tx.send(Err(e)).unwrap();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Ok(msg) = lua_rx.recv() {
|
||||||
|
match actor.handle_msg(msg) {
|
||||||
|
Err(e) => eprintln!("Error:{}", e),
|
||||||
|
Ok(true) => break,
|
||||||
|
Ok(false) => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
statup_rx.recv().unwrap()?;
|
||||||
|
|
||||||
|
Ok(LuaActorHandle { channel: lua_tx })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LuaActorHandle {
|
||||||
|
channel: Sender<LuaMsg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaActorHandle {
|
||||||
|
pub fn send(&self, msg: LuaMsg) {
|
||||||
|
self.channel.send(msg).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_steps_sync<F, T>(&self, f: F) -> T
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
for<'a> F: FnOnce(&'a mut Vec<Step>) -> T + Send + 'static,
|
||||||
|
{
|
||||||
|
let (tx, rx) = bounded(0);
|
||||||
|
|
||||||
|
self.send(LuaMsg::WithSteps(Box::new(move |steps| {
|
||||||
|
tx.send(f(steps)).unwrap()
|
||||||
|
})));
|
||||||
|
|
||||||
|
rx.recv().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LuaActor {
|
||||||
|
_lua: &'static Lua,
|
||||||
|
config: Config<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaActor {
|
||||||
|
fn handle_msg(&mut self, msg: LuaMsg) -> miette::Result<bool> {
|
||||||
|
match msg {
|
||||||
|
LuaMsg::Exit => return Ok(true),
|
||||||
|
LuaMsg::WithSteps(f) => f(&mut self.config.steps),
|
||||||
|
LuaMsg::GetShell(tx) => tx.send(self.config.shell.clone()).unwrap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init() -> miette::Result<Self> {
|
||||||
|
let cfg_path = dirs::config_dir()
|
||||||
|
.ok_or_else(|| miette!("Couldn't get config dir"))?
|
||||||
|
.join("upgr/config.lua");
|
||||||
|
|
||||||
|
let lua = unsafe { Lua::unsafe_new() };
|
||||||
|
let lua = Box::leak(Box::new(lua));
|
||||||
|
|
||||||
|
let config = libupgr::config::run_config(
|
||||||
|
lua,
|
||||||
|
&fs::read(cfg_path)
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Couldn't read config")?,
|
||||||
|
)
|
||||||
|
.wrap_err("Failed to run config")?;
|
||||||
|
|
||||||
|
Ok(LuaActor { _lua: lua, config })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LuaMsg {
|
||||||
|
Exit,
|
||||||
|
WithSteps(Box<dyn FnOnce(&mut Vec<Step>) + Send>),
|
||||||
|
GetShell(Sender<Vec<String>>),
|
||||||
|
}
|
32
gupgr/src/main.rs
Normal file
32
gupgr/src/main.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use miette::{miette, IntoDiagnostic, WrapErr};
|
||||||
|
use relm4::RelmApp;
|
||||||
|
use ui::{AppModel, IdxChanged};
|
||||||
|
|
||||||
|
mod lua_actor;
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
|
fn main() -> miette::Result<()> {
|
||||||
|
let lua_actor = lua_actor::start()?;
|
||||||
|
|
||||||
|
let steps = lua_actor.with_steps_sync(|steps| steps.len());
|
||||||
|
|
||||||
|
let model = AppModel {
|
||||||
|
db: sled::open(
|
||||||
|
dirs::cache_dir()
|
||||||
|
.ok_or_else(|| miette!("Couldn't get cache dir"))?
|
||||||
|
.join("upgr/db"),
|
||||||
|
)
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Couldn't open database")?,
|
||||||
|
can_run: steps >= 1,
|
||||||
|
lua_actor,
|
||||||
|
selected_idx: 0,
|
||||||
|
idx_changed: Cell::new(IdxChanged::Auto),
|
||||||
|
};
|
||||||
|
|
||||||
|
RelmApp::new(model).run();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
121
gupgr/src/ui/entry.rs
Normal file
121
gupgr/src/ui/entry.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use relm4::{
|
||||||
|
gtk::{self, prelude::*},
|
||||||
|
ComponentUpdate,
|
||||||
|
Model,
|
||||||
|
Widgets,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{AppModel, AppMsg};
|
||||||
|
|
||||||
|
pub struct EntryModel {
|
||||||
|
id: Option<usize>,
|
||||||
|
label: String,
|
||||||
|
workdir: Option<String>,
|
||||||
|
status: EntryStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model for EntryModel {
|
||||||
|
type Msg = EntryMsg;
|
||||||
|
type Widgets = EntryWidgets;
|
||||||
|
type Components = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EntryMsg {
|
||||||
|
SetInitialData {
|
||||||
|
id: usize,
|
||||||
|
label: String,
|
||||||
|
workdir: Option<String>,
|
||||||
|
},
|
||||||
|
SetStatus(EntryStatus),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentUpdate<AppModel> for EntryModel {
|
||||||
|
fn init_model(_parent_model: &AppModel) -> Self {
|
||||||
|
Self {
|
||||||
|
id: None,
|
||||||
|
label: String::new(),
|
||||||
|
workdir: None,
|
||||||
|
status: EntryStatus::Waiting,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Msg,
|
||||||
|
_components: &Self::Components,
|
||||||
|
_sender: relm4::Sender<Self::Msg>,
|
||||||
|
_parent_sender: relm4::Sender<AppMsg>,
|
||||||
|
) {
|
||||||
|
match msg {
|
||||||
|
EntryMsg::SetInitialData { id, label, workdir } => {
|
||||||
|
self.id = Some(id);
|
||||||
|
self.label = label;
|
||||||
|
self.workdir = workdir;
|
||||||
|
},
|
||||||
|
EntryMsg::SetStatus(status) => self.status = status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::widget(pub)]
|
||||||
|
impl Widgets<EntryModel, AppModel> for EntryWidgets {
|
||||||
|
view! {
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
|
||||||
|
append: stack = >k::Stack {
|
||||||
|
set_transition_type: gtk::StackTransitionType::SlideLeftRight,
|
||||||
|
|
||||||
|
add_child: waiting = >k::Image::from_icon_name("document-open-recent-symbolic") {},
|
||||||
|
add_child: running = >k::Spinner { set_spinning: true },
|
||||||
|
add_child: success = >k::Image::from_icon_name("object-select") {},
|
||||||
|
add_child: error = >k::Image::from_icon_name("dialog-error") {},
|
||||||
|
add_child: skipped = >k::Image::from_icon_name("media-skip-forward") {},
|
||||||
|
},
|
||||||
|
|
||||||
|
append = >k::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
|
||||||
|
append = >k::Label {
|
||||||
|
set_halign: gtk::Align::Start,
|
||||||
|
set_label: watch! { &model.label },
|
||||||
|
},
|
||||||
|
|
||||||
|
append = >k::Label {
|
||||||
|
set_halign: gtk::Align::Start,
|
||||||
|
add_css_class: "dim-label",
|
||||||
|
set_label: watch! {
|
||||||
|
&model
|
||||||
|
.workdir
|
||||||
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
std::env::current_dir()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|_| String::new(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_view() {
|
||||||
|
match model.status {
|
||||||
|
EntryStatus::Waiting => self.stack.set_visible_child(&self.waiting),
|
||||||
|
EntryStatus::Running => self.stack.set_visible_child(&self.running),
|
||||||
|
EntryStatus::Success => self.stack.set_visible_child(&self.success),
|
||||||
|
EntryStatus::Error => self.stack.set_visible_child(&self.error),
|
||||||
|
EntryStatus::Skipped => self.stack.set_visible_child(&self.skipped),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EntryStatus {
|
||||||
|
Waiting,
|
||||||
|
Running,
|
||||||
|
Success,
|
||||||
|
Error,
|
||||||
|
Skipped,
|
||||||
|
}
|
293
gupgr/src/ui/mod.rs
Normal file
293
gupgr/src/ui/mod.rs
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use crossbeam_channel::bounded;
|
||||||
|
use libupgr::config::CfgCommand;
|
||||||
|
use relm4::{
|
||||||
|
gtk::{self, prelude::*},
|
||||||
|
send,
|
||||||
|
AppUpdate,
|
||||||
|
Components,
|
||||||
|
Model,
|
||||||
|
RelmComponent,
|
||||||
|
Widgets,
|
||||||
|
};
|
||||||
|
use sled::Db;
|
||||||
|
|
||||||
|
use crate::lua_actor::{LuaActorHandle, LuaMsg};
|
||||||
|
|
||||||
|
use self::{
|
||||||
|
entry::{EntryModel, EntryMsg, EntryStatus},
|
||||||
|
terminal::{SpawnData, TerminalModel, TerminalMsg},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod entry;
|
||||||
|
mod terminal;
|
||||||
|
|
||||||
|
pub struct AppModel {
|
||||||
|
pub db: Db,
|
||||||
|
pub can_run: bool,
|
||||||
|
pub lua_actor: LuaActorHandle,
|
||||||
|
pub selected_idx: i32,
|
||||||
|
pub idx_changed: Cell<IdxChanged>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model for AppModel {
|
||||||
|
type Msg = AppMsg;
|
||||||
|
type Widgets = AppWidgets;
|
||||||
|
type Components = AppComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppUpdate for AppModel {
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Msg,
|
||||||
|
components: &Self::Components,
|
||||||
|
sender: relm4::Sender<Self::Msg>,
|
||||||
|
) -> bool {
|
||||||
|
match msg {
|
||||||
|
AppMsg::TerminalExited { id, success } => {
|
||||||
|
if let Some(e) = components.entries.get(id) {
|
||||||
|
e.send(EntryMsg::SetStatus(if success {
|
||||||
|
EntryStatus::Success
|
||||||
|
} else {
|
||||||
|
EntryStatus::Error
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if id + 1 >= components.terminals.len() {
|
||||||
|
self.can_run = true;
|
||||||
|
} else {
|
||||||
|
send!(sender, AppMsg::RunId(id + 1));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
AppMsg::RunId(id) => {
|
||||||
|
self.can_run = false;
|
||||||
|
if id == 0 {
|
||||||
|
for e in &components.entries {
|
||||||
|
e.send(EntryMsg::SetStatus(EntryStatus::Waiting)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
components.entries[id]
|
||||||
|
.send(EntryMsg::SetStatus(EntryStatus::Running))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
send!(sender, AppMsg::RowSelected(id as i32, IdxChanged::Auto));
|
||||||
|
|
||||||
|
self.lua_actor
|
||||||
|
.send(LuaMsg::WithSteps(Box::new(move |steps| {
|
||||||
|
if let Some(when) = &steps[id].when {
|
||||||
|
if when.call(()).unwrap_or(false) {
|
||||||
|
send!(sender, AppMsg::ExecId(id));
|
||||||
|
} else {
|
||||||
|
send!(sender, AppMsg::SkipId(id));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send!(sender, AppMsg::ExecId(id));
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
|
||||||
|
AppMsg::SkipId(id) => {
|
||||||
|
components.entries[id]
|
||||||
|
.send(EntryMsg::SetStatus(EntryStatus::Skipped))
|
||||||
|
.unwrap();
|
||||||
|
if id + 1 >= components.terminals.len() {
|
||||||
|
self.can_run = true;
|
||||||
|
} else {
|
||||||
|
send!(sender, AppMsg::RunId(id + 1));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
AppMsg::ExecId(id) => {
|
||||||
|
components.entries[id]
|
||||||
|
.send(EntryMsg::SetStatus(EntryStatus::Running))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (tx, rx) = bounded(0);
|
||||||
|
self.lua_actor.send(LuaMsg::GetShell(tx));
|
||||||
|
let mut shell = rx.recv().unwrap();
|
||||||
|
|
||||||
|
let (argv, workdir) = self.lua_actor.with_steps_sync(move |steps| {
|
||||||
|
let step = &steps[id];
|
||||||
|
|
||||||
|
let argv = match step.command.clone() {
|
||||||
|
CfgCommand::Shell(c) => {
|
||||||
|
shell.push(c);
|
||||||
|
shell
|
||||||
|
},
|
||||||
|
CfgCommand::Args(a) => a,
|
||||||
|
};
|
||||||
|
|
||||||
|
(argv, step.workdir.clone().map(Into::into))
|
||||||
|
});
|
||||||
|
|
||||||
|
components.terminals[id]
|
||||||
|
.send(TerminalMsg::Spawn(SpawnData { argv, workdir }))
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
AppMsg::RowSelected(idx, idxc) => {
|
||||||
|
self.selected_idx = idx;
|
||||||
|
self.idx_changed.set(idxc);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::widget(pub)]
|
||||||
|
impl Widgets<AppModel, ()> for AppWidgets {
|
||||||
|
view! {
|
||||||
|
main_window = gtk::ApplicationWindow {
|
||||||
|
set_title: Some("gupgr"),
|
||||||
|
|
||||||
|
set_child = Some(>k::Box) {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
|
||||||
|
append = >k::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
|
||||||
|
append = >k::ScrolledWindow {
|
||||||
|
set_vexpand: true,
|
||||||
|
set_width_request: 500,
|
||||||
|
|
||||||
|
set_child: list_box = Some(>k::ListBox) {
|
||||||
|
append: iterate!(
|
||||||
|
components
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.map(RelmComponent::root_widget)
|
||||||
|
),
|
||||||
|
|
||||||
|
connect_row_selected(sender) => move |_, row| {
|
||||||
|
if let Some(row) = row {
|
||||||
|
send!(sender, AppMsg::RowSelected(row.index(), IdxChanged::Manual));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Run!",
|
||||||
|
|
||||||
|
set_sensitive: watch!(model.can_run),
|
||||||
|
|
||||||
|
connect_clicked(sender) => move |_| send!(sender, AppMsg::RunId(0)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
append: stack = >k::Stack {
|
||||||
|
set_transition_type: gtk::StackTransitionType::SlideUpDown,
|
||||||
|
add_child: iterate!(
|
||||||
|
components
|
||||||
|
.terminals
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
let widget = t.root_widget();
|
||||||
|
stack_entries.push(widget.clone());
|
||||||
|
widget
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
additional_fields! {
|
||||||
|
stack_entries: Vec<vte4::Terminal>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_init() {
|
||||||
|
let mut stack_entries = vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_view() {
|
||||||
|
match model.idx_changed.take() {
|
||||||
|
IdxChanged::No => {},
|
||||||
|
IdxChanged::Manual => {
|
||||||
|
self.stack
|
||||||
|
.set_visible_child(&self.stack_entries[model.selected_idx as usize]);
|
||||||
|
},
|
||||||
|
IdxChanged::Auto => self
|
||||||
|
.list_box
|
||||||
|
.select_row(self.list_box.row_at_index(model.selected_idx).as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AppMsg {
|
||||||
|
TerminalExited { id: usize, success: bool },
|
||||||
|
RunId(usize),
|
||||||
|
SkipId(usize),
|
||||||
|
ExecId(usize),
|
||||||
|
RowSelected(i32, IdxChanged),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppComponents {
|
||||||
|
entries: Vec<RelmComponent<EntryModel, AppModel>>,
|
||||||
|
terminals: Vec<RelmComponent<TerminalModel, AppModel>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Components<AppModel> for AppComponents {
|
||||||
|
fn init_components(parent_model: &AppModel, parent_sender: relm4::Sender<AppMsg>) -> Self {
|
||||||
|
let msgs = parent_model.lua_actor.with_steps_sync(|steps| {
|
||||||
|
steps
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, step)| {
|
||||||
|
(
|
||||||
|
EntryMsg::SetInitialData {
|
||||||
|
id,
|
||||||
|
label: step.command.to_string(),
|
||||||
|
workdir: step
|
||||||
|
.workdir
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| d.to_string_lossy().to_string()),
|
||||||
|
},
|
||||||
|
TerminalMsg::SetId(id),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut entries = Vec::with_capacity(msgs.len());
|
||||||
|
let mut terminals = Vec::with_capacity(msgs.len());
|
||||||
|
|
||||||
|
for (e_msg, t_msg) in msgs {
|
||||||
|
let entr = RelmComponent::new(parent_model, parent_sender.clone());
|
||||||
|
let term = RelmComponent::new(parent_model, parent_sender.clone());
|
||||||
|
|
||||||
|
entr.send(e_msg).unwrap();
|
||||||
|
term.send(t_msg).unwrap();
|
||||||
|
|
||||||
|
entries.push(entr);
|
||||||
|
terminals.push(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { entries, terminals }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_parent(&mut self, parent_widgets: &AppWidgets) {
|
||||||
|
for entry in &mut self.entries {
|
||||||
|
entry.connect_parent(parent_widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
for terminal in &mut self.terminals {
|
||||||
|
terminal.connect_parent(parent_widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum IdxChanged {
|
||||||
|
No,
|
||||||
|
Auto,
|
||||||
|
Manual,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IdxChanged {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::No
|
||||||
|
}
|
||||||
|
}
|
107
gupgr/src/ui/terminal.rs
Normal file
107
gupgr/src/ui/terminal.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
use std::{cell::RefCell, ffi::OsString, os::unix::ffi::OsStrExt, path::Path};
|
||||||
|
|
||||||
|
use relm4::{
|
||||||
|
gtk::{self, glib::SpawnFlags, prelude::*},
|
||||||
|
send,
|
||||||
|
ComponentUpdate,
|
||||||
|
Model,
|
||||||
|
Widgets,
|
||||||
|
};
|
||||||
|
use vte4::{PtyFlags, TerminalExt};
|
||||||
|
|
||||||
|
use super::{AppModel, AppMsg};
|
||||||
|
|
||||||
|
pub struct TerminalModel {
|
||||||
|
id: Option<usize>,
|
||||||
|
do_spawn: RefCell<Option<SpawnData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model for TerminalModel {
|
||||||
|
type Msg = TerminalMsg;
|
||||||
|
type Widgets = TerminalWidgets;
|
||||||
|
type Components = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TerminalMsg {
|
||||||
|
SetId(usize),
|
||||||
|
Exit(i32),
|
||||||
|
Spawn(SpawnData),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SpawnData {
|
||||||
|
pub argv: Vec<String>,
|
||||||
|
pub workdir: Option<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentUpdate<AppModel> for TerminalModel {
|
||||||
|
fn init_model(_parent_model: &AppModel) -> Self {
|
||||||
|
Self {
|
||||||
|
id: None,
|
||||||
|
do_spawn: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Msg,
|
||||||
|
_components: &Self::Components,
|
||||||
|
_sender: relm4::Sender<Self::Msg>,
|
||||||
|
parent_sender: relm4::Sender<AppMsg>,
|
||||||
|
) {
|
||||||
|
match msg {
|
||||||
|
TerminalMsg::SetId(id) => self.id = Some(id),
|
||||||
|
TerminalMsg::Exit(status) => {
|
||||||
|
send!(
|
||||||
|
parent_sender,
|
||||||
|
AppMsg::TerminalExited {
|
||||||
|
id: self.id.unwrap(),
|
||||||
|
success: status == 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
TerminalMsg::Spawn(s) => *self.do_spawn.borrow_mut() = Some(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::widget(pub)]
|
||||||
|
impl Widgets<TerminalModel, AppModel> for TerminalWidgets {
|
||||||
|
view! {
|
||||||
|
term = vte4::Terminal {
|
||||||
|
set_hexpand: true,
|
||||||
|
set_vexpand: true,
|
||||||
|
|
||||||
|
connect_child_exited(sender) => move |_, status| {
|
||||||
|
send!(sender, TerminalMsg::Exit(status));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_view() {
|
||||||
|
if let Some(SpawnData { argv, workdir }) = model.do_spawn.take() {
|
||||||
|
let argv = argv.iter().map(Path::new).collect::<Vec<_>>();
|
||||||
|
let envv_data = std::env::vars_os()
|
||||||
|
.map(|(mut a, b)| {
|
||||||
|
a.push("=");
|
||||||
|
a.push(b);
|
||||||
|
a
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let envv = envv_data.iter().map(Path::new).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
self.term.spawn_async(
|
||||||
|
PtyFlags::empty(),
|
||||||
|
workdir
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) }),
|
||||||
|
&argv,
|
||||||
|
&envv,
|
||||||
|
SpawnFlags::DO_NOT_REAP_CHILD, // needed with child_exited is connected
|
||||||
|
Some(Box::new(|| {})),
|
||||||
|
-1,
|
||||||
|
gtk::gio::Cancellable::NONE,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
libupgr/Cargo.toml
Normal file
15
libupgr/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "libupgr"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
miette = { version = "3.2.0" }
|
||||||
|
mlua = { version = "0.7.3", features = ["luajit", "serialize"] }
|
||||||
|
serde = { version = "1.0.130", features = ["derive"] }
|
||||||
|
sled = "0.34.7"
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
walkdir = "2.3.2"
|
||||||
|
xxhash-rust = { version = "0.8.2", features = ["xxh3"] }
|
|
@ -1,13 +1,10 @@
|
||||||
use miette::{Context, Diagnostic, IntoDiagnostic};
|
use miette::{Context, Diagnostic, IntoDiagnostic};
|
||||||
use mlua::{prelude::*, Lua};
|
use mlua::{prelude::*, Lua};
|
||||||
use owo_colors::OwoColorize;
|
|
||||||
use std::{path::PathBuf, process::Command};
|
use std::{path::PathBuf, process::Command};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::info_table::ToInfoData;
|
|
||||||
|
|
||||||
pub struct Config<'lua> {
|
pub struct Config<'lua> {
|
||||||
pub steps: Vec<Step<'lua>>,
|
pub steps: Vec<Step<'lua>>,
|
||||||
pub shell: Vec<String>,
|
pub shell: Vec<String>,
|
||||||
|
@ -25,7 +22,7 @@ pub struct Step<'lua> {
|
||||||
pub when: Option<LuaFunction<'lua>>,
|
pub when: Option<LuaFunction<'lua>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum CfgCommand {
|
pub enum CfgCommand {
|
||||||
Shell(String),
|
Shell(String),
|
||||||
|
@ -41,22 +38,6 @@ impl ToString for CfgCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToInfoData for CfgCommand {
|
|
||||||
fn to_info_data(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Self::Shell(s) => s.green().to_string(),
|
|
||||||
Self::Args(args) => args.join(" ").green().to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn info_width(&self) -> Option<usize> {
|
|
||||||
Some(match self {
|
|
||||||
Self::Shell(s) => s.len(),
|
|
||||||
Self::Args(a) => a.iter().map(String::len).sum::<usize>() + a.len() - 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
#[diagnostic(code)]
|
#[diagnostic(code)]
|
||||||
pub enum IntoCommandError {
|
pub enum IntoCommandError {
|
24
libupgr/src/lib.rs
Normal file
24
libupgr/src/lib.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod luautil;
|
||||||
|
|
||||||
|
pub enum PathStatus {
|
||||||
|
NonExistant,
|
||||||
|
Directory,
|
||||||
|
File,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PathExt {
|
||||||
|
fn status(&self) -> PathStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathExt for Path {
|
||||||
|
fn status(&self) -> PathStatus {
|
||||||
|
match self.metadata() {
|
||||||
|
Ok(m) if m.is_file() => PathStatus::File,
|
||||||
|
Ok(m) if m.is_dir() => PathStatus::Directory,
|
||||||
|
_ => PathStatus::NonExistant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ pub fn get_obj(lua: &Lua, db: Rc<sled::Db>) -> miette::Result<LuaTable> {
|
||||||
fn l_has_changed(path_str: &str, db: Rc<sled::Tree>, ignored: HashSet<String>) -> LuaResult<bool> {
|
fn l_has_changed(path_str: &str, db: Rc<sled::Tree>, ignored: HashSet<String>) -> LuaResult<bool> {
|
||||||
let path = Path::new(path_str);
|
let path = Path::new(path_str);
|
||||||
let mut hasher = Xxh3::new();
|
let mut hasher = Xxh3::new();
|
||||||
let ignored = ignored.iter().map(|s| Path::new(s)).collect::<Vec<_>>();
|
let ignored = ignored.iter().map(Path::new).collect::<Vec<_>>();
|
||||||
|
|
||||||
match path.status() {
|
match path.status() {
|
||||||
PathStatus::NonExistant => Ok(false),
|
PathStatus::NonExistant => Ok(false),
|
108
src/lib.rs
108
src/lib.rs
|
@ -1,108 +0,0 @@
|
||||||
use crate::{
|
|
||||||
config::CfgCommand,
|
|
||||||
info_table::{CustomTableData, InfoTable, ToInfoData},
|
|
||||||
};
|
|
||||||
use config::Config;
|
|
||||||
use miette::{Context, IntoDiagnostic};
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use owo_colors::OwoColorize;
|
|
||||||
use std::{
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod config;
|
|
||||||
pub mod info_table;
|
|
||||||
pub mod luautil;
|
|
||||||
|
|
||||||
pub fn exec_steps(conf: Config, uninteractive: bool) -> miette::Result<()> {
|
|
||||||
for step in conf.steps {
|
|
||||||
if step.interactive && uninteractive {
|
|
||||||
if let Some(cmd) = step.unint_alt {
|
|
||||||
exec_cmd(cmd, &conf.shell, step.when, step.workdir)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
exec_cmd(step.command, &conf.shell, step.when, step.workdir)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exec_cmd(
|
|
||||||
cmd: CfgCommand,
|
|
||||||
shell: &[String],
|
|
||||||
when: Option<LuaFunction>,
|
|
||||||
workdir: Option<PathBuf>,
|
|
||||||
) -> miette::Result<()> {
|
|
||||||
if let Some(func) = when {
|
|
||||||
let val: bool = func
|
|
||||||
.call(())
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("Failed to call when function")?;
|
|
||||||
|
|
||||||
if !val {
|
|
||||||
println!("Skipping {}", cmd.to_info_data());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut table = InfoTable::new_with_title("Running");
|
|
||||||
table.row("Command", &cmd);
|
|
||||||
|
|
||||||
let mut cmd = cmd.into_command(shell)?;
|
|
||||||
if let Some(workdir) = workdir {
|
|
||||||
table.row("Workdir", &workdir.to_string_lossy().as_ref());
|
|
||||||
cmd.current_dir(workdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", table.to_string());
|
|
||||||
|
|
||||||
let start_time = Instant::now();
|
|
||||||
let exit = cmd.spawn().into_diagnostic()?.wait().into_diagnostic()?;
|
|
||||||
let total_time = Instant::now() - start_time;
|
|
||||||
|
|
||||||
let mut data = String::new();
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
InfoTable::new_with_title("Summary")
|
|
||||||
.row("Time", &total_time)
|
|
||||||
.row(
|
|
||||||
"Exit Code",
|
|
||||||
&exit
|
|
||||||
.code()
|
|
||||||
.map(|s| if s == 0 {
|
|
||||||
data = 0.green().to_string();
|
|
||||||
CustomTableData(&data, 1)
|
|
||||||
} else {
|
|
||||||
let s = s.to_string();
|
|
||||||
data = s.red().to_string();
|
|
||||||
CustomTableData(&data, s.len())
|
|
||||||
})
|
|
||||||
.unwrap_or(CustomTableData("Unknown", 7))
|
|
||||||
)
|
|
||||||
.to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum PathStatus {
|
|
||||||
NonExistant,
|
|
||||||
Directory,
|
|
||||||
File,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PathExt {
|
|
||||||
fn status(&self) -> PathStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathExt for Path {
|
|
||||||
fn status(&self) -> PathStatus {
|
|
||||||
match self.metadata() {
|
|
||||||
Ok(m) if m.is_file() => PathStatus::File,
|
|
||||||
Ok(m) if m.is_dir() => PathStatus::Directory,
|
|
||||||
_ => PathStatus::NonExistant,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
64
src/main.rs
64
src/main.rs
|
@ -1,64 +0,0 @@
|
||||||
use clap::Parser;
|
|
||||||
use miette::{miette, Context, IntoDiagnostic};
|
|
||||||
use mlua::Lua;
|
|
||||||
use std::{fs::File, io::Read, path::PathBuf, rc::Rc};
|
|
||||||
use upgr::luautil;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
struct Opt {
|
|
||||||
#[clap(long, short)]
|
|
||||||
/// Don't run interactive steps, unless they have unint_alt set
|
|
||||||
uninteractive: bool,
|
|
||||||
|
|
||||||
#[clap(long, short)]
|
|
||||||
/// The config file to use. Defaults to ~/.config/upgr/config.lua
|
|
||||||
config: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> miette::Result<()> {
|
|
||||||
let opt = Opt::parse();
|
|
||||||
|
|
||||||
let cfg_path = if let Some(path) = opt.config {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
dirs::config_dir()
|
|
||||||
.ok_or_else(|| miette!("Couldn't get config directory"))?
|
|
||||||
.join("upgr/config.lua")
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut file = File::open(cfg_path)
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("Couldn't open config")?;
|
|
||||||
|
|
||||||
let mut config_bytes = vec![];
|
|
||||||
file.read_to_end(&mut config_bytes)
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("Failed to read config")?;
|
|
||||||
|
|
||||||
let db = Rc::new(
|
|
||||||
sled::open(
|
|
||||||
dirs::cache_dir()
|
|
||||||
.ok_or_else(|| miette!("Couldn't get cache dir"))?
|
|
||||||
.join("upgr/db"),
|
|
||||||
)
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("Couldn't open database")?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let lua = unsafe { Lua::unsafe_new() };
|
|
||||||
lua.globals()
|
|
||||||
.set(
|
|
||||||
"upgr",
|
|
||||||
luautil::get_obj(&lua, Rc::clone(&db)).wrap_err("Couldn't create lua upgr object")?,
|
|
||||||
)
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("Couldn't set upgr lua object")?;
|
|
||||||
|
|
||||||
let config = upgr::config::run_config(&lua, &config_bytes).wrap_err("Failed to run config")?;
|
|
||||||
upgr::exec_steps(config, opt.uninteractive).wrap_err("Failed to execute steps")?;
|
|
||||||
db.flush()
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("Failed to flush DB")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
17
upgr/Cargo.toml
Normal file
17
upgr/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "upgr"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "3.0.0-beta.5", features = ["derive"] }
|
||||||
|
dirs = "4.0.0"
|
||||||
|
libupgr = { path = "../libupgr" }
|
||||||
|
miette = { version = "3.2.0", features = ["fancy"] }
|
||||||
|
mlua = { version = "0.7.3", features = ["luajit", "serialize"] }
|
||||||
|
owo-colors = "3.1.0"
|
||||||
|
sled = "0.34.7"
|
||||||
|
|
||||||
|
[features]
|
160
upgr/src/main.rs
Normal file
160
upgr/src/main.rs
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
use crate::info_table::{CustomTableData, InfoTable, ToInfoData};
|
||||||
|
use clap::Parser;
|
||||||
|
use libupgr::{
|
||||||
|
config::{CfgCommand, Config},
|
||||||
|
luautil,
|
||||||
|
};
|
||||||
|
use miette::{miette, Context, IntoDiagnostic};
|
||||||
|
use mlua::{prelude::LuaFunction, Lua};
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
|
use std::{fs::File, io::Read, path::PathBuf, rc::Rc, time::Instant};
|
||||||
|
|
||||||
|
mod info_table;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Opt {
|
||||||
|
#[clap(long, short)]
|
||||||
|
/// Don't run interactive steps, unless they have unint_alt set
|
||||||
|
uninteractive: bool,
|
||||||
|
|
||||||
|
#[clap(long, short)]
|
||||||
|
/// The config file to use. Defaults to ~/.config/upgr/config.lua
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> miette::Result<()> {
|
||||||
|
let opt = Opt::parse();
|
||||||
|
|
||||||
|
let cfg_path = if let Some(path) = opt.config {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
dirs::config_dir()
|
||||||
|
.ok_or_else(|| miette!("Couldn't get config directory"))?
|
||||||
|
.join("upgr/config.lua")
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = File::open(cfg_path)
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Couldn't open config")?;
|
||||||
|
|
||||||
|
let mut config_bytes = vec![];
|
||||||
|
file.read_to_end(&mut config_bytes)
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Failed to read config")?;
|
||||||
|
|
||||||
|
let db = Rc::new(
|
||||||
|
sled::open(
|
||||||
|
dirs::cache_dir()
|
||||||
|
.ok_or_else(|| miette!("Couldn't get cache dir"))?
|
||||||
|
.join("upgr/db"),
|
||||||
|
)
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Couldn't open database")?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let lua = unsafe { Lua::unsafe_new() };
|
||||||
|
lua.globals()
|
||||||
|
.set(
|
||||||
|
"upgr",
|
||||||
|
luautil::get_obj(&lua, Rc::clone(&db)).wrap_err("Couldn't create lua upgr object")?,
|
||||||
|
)
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Couldn't set upgr lua object")?;
|
||||||
|
|
||||||
|
let config =
|
||||||
|
libupgr::config::run_config(&lua, &config_bytes).wrap_err("Failed to run config")?;
|
||||||
|
exec_steps(config, opt.uninteractive).wrap_err("Failed to execute steps")?;
|
||||||
|
db.flush()
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Failed to flush DB")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec_steps(conf: Config, uninteractive: bool) -> miette::Result<()> {
|
||||||
|
for step in conf.steps {
|
||||||
|
if step.interactive && uninteractive {
|
||||||
|
if let Some(cmd) = step.unint_alt {
|
||||||
|
exec_cmd(cmd, &conf.shell, step.when, step.workdir)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exec_cmd(step.command, &conf.shell, step.when, step.workdir)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec_cmd(
|
||||||
|
cmd: CfgCommand,
|
||||||
|
shell: &[String],
|
||||||
|
when: Option<LuaFunction>,
|
||||||
|
workdir: Option<PathBuf>,
|
||||||
|
) -> miette::Result<()> {
|
||||||
|
if let Some(func) = when {
|
||||||
|
let val: bool = func
|
||||||
|
.call(())
|
||||||
|
.into_diagnostic()
|
||||||
|
.wrap_err("Failed to call when function")?;
|
||||||
|
|
||||||
|
if !val {
|
||||||
|
println!("Skipping {}", cmd.to_info_data());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = InfoTable::new_with_title("Running");
|
||||||
|
table.row("Command", &cmd);
|
||||||
|
|
||||||
|
let mut cmd = cmd.into_command(shell)?;
|
||||||
|
if let Some(workdir) = workdir {
|
||||||
|
table.row("Workdir", &workdir.to_string_lossy().as_ref());
|
||||||
|
cmd.current_dir(workdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", table.to_string());
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
let exit = cmd.spawn().into_diagnostic()?.wait().into_diagnostic()?;
|
||||||
|
let total_time = Instant::now() - start_time;
|
||||||
|
|
||||||
|
let mut data = String::new();
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
InfoTable::new_with_title("Summary")
|
||||||
|
.row("Time", &total_time)
|
||||||
|
.row(
|
||||||
|
"Exit Code",
|
||||||
|
&exit
|
||||||
|
.code()
|
||||||
|
.map(|s| if s == 0 {
|
||||||
|
data = 0.green().to_string();
|
||||||
|
CustomTableData(&data, 1)
|
||||||
|
} else {
|
||||||
|
let s = s.to_string();
|
||||||
|
data = s.red().to_string();
|
||||||
|
CustomTableData(&data, s.len())
|
||||||
|
})
|
||||||
|
.unwrap_or(CustomTableData("Unknown", 7))
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToInfoData for CfgCommand {
|
||||||
|
fn to_info_data(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Shell(s) => s.green().to_string(),
|
||||||
|
Self::Args(args) => args.join(" ").green().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_width(&self) -> Option<usize> {
|
||||||
|
Some(match self {
|
||||||
|
Self::Shell(s) => s.len(),
|
||||||
|
Self::Args(a) => a.iter().map(String::len).sum::<usize>() + a.len() - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue