feat: update adtools

This commit is contained in:
anjingyu 2024-06-26 11:42:47 +08:00
parent 812cc22990
commit 32370295f7
16 changed files with 619 additions and 9 deletions

View File

@ -21,4 +21,4 @@
# Copyright (C) 2021- donkey <anjingyu_ws@foxmail.com>
__author__ = ['"donkey" <anjingyu_ws@foxmail.com>']
__version__ = "0.5.45"
__version__ = "0.5.46"

View File

@ -123,7 +123,7 @@ class Android(OSBase):
def run_make(self, args):
return AdUtil.run_command(
"cmake --build . --config {} -- -j{}".format(
"Release" if args.release else "Debug", AdUtil.get_cpu_number()
"Release" if args.release else "Debug", AdUtil.get_recommand_cpu_number()
)
)[0]

View File

@ -50,13 +50,15 @@ class Linux(OSBase):
return '-G "Unix Makefiles"'
def update_argument(self, parser):
default_job_count = Adutil.get_recommand_cpu_number()
parser.add_argument(
"-j",
"--jobs",
default=AdUtil.get_cpu_number(),
default=default_job_count,
type=int,
help="Specify the count of cocurrent jobs, default: <core count>, {}".format(
AdUtil.get_cpu_number()
default_job_count
),
)

View File

@ -105,6 +105,6 @@ please make sure the environment variable *QNX_ROOT* is defined to point to the
else:
qnx_make = os.path.join(qnx_host_bin, "make")
return AdUtil.run_command("{} -j{}".format(qnx_make, AdUtil.get_cpu_number()))[
return AdUtil.run_command("{} -j{}".format(qnx_make, Adutil.get_recommand_cpu_number()))[
0
]

View File

@ -105,6 +105,6 @@ please make sure the environment variable *QNX7_ROOT* is defined to point to the
else:
qnx_make = os.path.join(qnx_host_bin, "make")
return AdUtil.run_command("{} -j{}".format(qnx_make, AdUtil.get_cpu_number()))[
return AdUtil.run_command("{} -j{}".format(qnx_make, Adutil.get_recommand_cpu_number()))[
0
]

View File

@ -396,9 +396,23 @@ class AdUtil:
return ""
@classmethod
def get_cpu_number(cls):
def get_cpu_number(cls) -> int:
return multiprocessing.cpu_count()
@classmethod
def get_recommand_cpu_number(cls) -> int:
'''Get the recommended CPU core count for tasks,
so that the tasks will never freeze the entire system'''
default_job_count = AdUtil.get_cpu_number()
if default_job_count > 8:
default_job_count = 8
elif default_job_count > 2:
default_job_count = default_job_count - 2
else:
default_job_count = 1
return default_job_count
class NexusHelper:
def __init__(self, host: str = "", auth_info: tuple = ()):

View File

@ -6,6 +6,14 @@ edition = "2021"
[dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.4", features = ["derive"] }
color-eyre = "0.6.3"
crossterm = "0.27.0"
directories = "5.0.1"
futures = "0.3.30"
ratatui = "0.26.3"
serde = { version = "1.0.202", features = ["derive"] }
serde_derive = "1.0.203"
thiserror = "1.0.61"
tokio = "1.38.0"
tokio-util = "0.7.11"
toml = "0.8.13"

10
ins/app/README.md Normal file
View File

@ -0,0 +1,10 @@
# INS App TUI
``` shell
# Append dependences
# Required
cargo add ratatui crossterm tokio tokio_util futures
# Optional
cargo add color-eyre serde serde_derive directories
```

35
ins/app/src/app.rs Normal file
View File

@ -0,0 +1,35 @@
use std::io::{self, stdout};
use color_eyre::{config::HookBuilder, Result};
use ratatui::{
backend::{Backend, CrosstermBackend},
buffer::Buffer,
crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
},
layout::{
Alignment,
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
Flex, Layout, Rect,
},
style::{palette::tailwind, Color, Modifier, Style, Stylize},
symbols::{self, line},
terminal::Terminal,
text::{Line, Text},
widgets::{
block::Title, Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState,
StatefulWidget, Tabs, Widget,
},
};
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
#[derive(Default, Clone, Copy)]
struct App {
selected_tab: SelectedTab,
scroll_offset: u16,
spacing: u16,
state: AppState,
}

View File

@ -6,9 +6,32 @@ use std::{
};
use anyhow::{anyhow, Context, Result};
use directories::ProjectDirs;
use serde::Deserialize;
use toml::Value;
pub(crate) fn get_data_dir() -> Result<PathBuf> {
let directory = if let Ok(s) = std::env::var("INSAPP_DATA") {
PathBuf::from(s)
} else if let Some(proj_dirs) = ProjectDirs::from("com", "donkey", "insapp") {
proj_dirs.data_local_dir().to_path_buf()
} else {
return Err(anyhow!("Unable to find data directory for INS App"));
};
Ok(directory)
}
pub(crate) fn get_config_dir() -> Result<PathBuf> {
let directory = if let Ok(s) = std::env::var("INSAPP_CONFIG") {
PathBuf::from(s)
} else if let Some(proj_dirs) = ProjectDirs::from("com", "donkey", "insapp") {
proj_dirs.config_local_dir().to_path_buf()
} else {
return Err(anyhow!("Unable to find config directory for INS App"));
};
Ok(directory)
}
#[derive(Debug, Deserialize)]
pub(crate) struct InsSerialConfig {
parser: String, // "gsof"

View File

@ -1,7 +1,43 @@
use anyhow::Result;
use insapp::config::Config;
use clap::{arg, ArgMatches, Command};
mod tui;
impl App {
async fn run(&mut self) -> Result<()> {
let mut tui = tui::Tui::new()?
.tick_rate(4.0) // 4 ticks per second
.frame_rate(30.0); // 30 frames per second
tui.enter()?; // Starts event handler, enters raw mode, enters alternate screen
loop {
tui.draw(|f| {
// Deref allows calling `tui.terminal.draw`
self.ui(f);
})?;
if let Some(evt) = tui.next().await {
// `tui.next().await` blocks till next event
let mut maybe_action = self.handle_event(evt);
while let Some(action) = maybe_action {
maybe_action = self.update(action);
}
};
if self.should_quit {
break;
}
}
tui.exit()?; // stops event handler, exits raw mode, exits alternate screen
Ok(())
}
}
struct ArgParser {
config_file: String,
}
@ -31,7 +67,7 @@ impl ArgParser {
}
}
fn main() {
fn main() -> Result<()> {
let arg_parser = ArgParser::new();
let matches = arg_parser.get_matches();
@ -42,5 +78,9 @@ fn main() {
let config = Config::load_from_file(config_file_path).unwrap();
println!("config: {:#?}", config);
init_error_hooks()?;
let terminal = init_terminal()?;
App::default().run(terminal)?;
restore_terminal()?;
Ok(())
}

243
ins/app/src/tui.rs Normal file
View File

@ -0,0 +1,243 @@
use std::{
ops::{Deref, DerefMut},
time::Duration,
};
use color_eyre::eyre::Result;
use crossterm::{
cursor,
event::{
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent,
},
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
};
use futures::{FutureExt, StreamExt};
use ratatui::backend::CrosstermBackend as Backend;
use serde::{Deserialize, Serialize};
use tokio::{
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
task::JoinHandle,
};
use tokio_util::sync::CancellationToken;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Event {
Init,
Quit,
Error,
Closed,
Tick,
Render,
FocusGained,
FocusLost,
Paste(String),
Key(KeyEvent),
Mouse(MouseEvent),
Resize(u16, u16),
}
pub struct Tui {
pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
pub task: JoinHandle<()>,
pub cancellation_token: CancellationToken,
pub event_rx: UnboundedReceiver<Event>,
pub event_tx: UnboundedSender<Event>,
pub frame_rate: f64,
pub tick_rate: f64,
pub mouse: bool,
pub paste: bool,
}
impl Tui {
pub fn new() -> Result<Self> {
let tick_rate = 4.0;
let frame_rate = 60.0;
let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
let (event_tx, event_rx) = mpsc::unbounded_channel();
let cancellation_token = CancellationToken::new();
let task = tokio::spawn(async {});
let mouse = false;
let paste = false;
Ok(Self {
terminal,
task,
cancellation_token,
event_rx,
event_tx,
frame_rate,
tick_rate,
mouse,
paste,
})
}
pub fn tick_rate(mut self, tick_rate: f64) -> Self {
self.tick_rate = tick_rate;
self
}
pub fn frame_rate(mut self, frame_rate: f64) -> Self {
self.frame_rate = frame_rate;
self
}
pub fn mouse(mut self, mouse: bool) -> Self {
self.mouse = mouse;
self
}
pub fn paste(mut self, paste: bool) -> Self {
self.paste = paste;
self
}
pub fn start(&mut self) {
let tick_delay = std::time::Duration::from_secs_f64(1.0 / self.tick_rate);
let render_delay = std::time::Duration::from_secs_f64(1.0 / self.frame_rate);
self.cancel();
self.cancellation_token = CancellationToken::new();
let _cancellation_token = self.cancellation_token.clone();
let _event_tx = self.event_tx.clone();
self.task = tokio::spawn(async move {
let mut reader = crossterm::event::EventStream::new();
let mut tick_interval = tokio::time::interval(tick_delay);
let mut render_interval = tokio::time::interval(render_delay);
_event_tx.send(Event::Init).unwrap();
loop {
let tick_delay = tick_interval.tick();
let render_delay = render_interval.tick();
let crossterm_event = reader.next().fuse();
tokio::select! {
_ = _cancellation_token.cancelled() => {
break;
}
maybe_event = crossterm_event => {
match maybe_event {
Some(Ok(evt)) => {
match evt {
CrosstermEvent::Key(key) => {
if key.kind == KeyEventKind::Press {
_event_tx.send(Event::Key(key)).unwrap();
}
},
CrosstermEvent::Mouse(mouse) => {
_event_tx.send(Event::Mouse(mouse)).unwrap();
},
CrosstermEvent::Resize(x, y) => {
_event_tx.send(Event::Resize(x, y)).unwrap();
},
CrosstermEvent::FocusLost => {
_event_tx.send(Event::FocusLost).unwrap();
},
CrosstermEvent::FocusGained => {
_event_tx.send(Event::FocusGained).unwrap();
},
CrosstermEvent::Paste(s) => {
_event_tx.send(Event::Paste(s)).unwrap();
},
}
}
Some(Err(_)) => {
_event_tx.send(Event::Error).unwrap();
}
None => {},
}
},
_ = tick_delay => {
_event_tx.send(Event::Tick).unwrap();
},
_ = render_delay => {
_event_tx.send(Event::Render).unwrap();
},
}
}
});
}
pub fn stop(&self) -> Result<()> {
self.cancel();
let mut counter = 0;
while !self.task.is_finished() {
std::thread::sleep(Duration::from_millis(1));
counter += 1;
if counter > 50 {
self.task.abort();
}
if counter > 100 {
log::error!("Failed to abort task in 100 milliseconds for unknown reason");
break;
}
}
Ok(())
}
pub fn enter(&mut self) -> Result<()> {
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
if self.mouse {
crossterm::execute!(std::io::stderr(), EnableMouseCapture)?;
}
if self.paste {
crossterm::execute!(std::io::stderr(), EnableBracketedPaste)?;
}
self.start();
Ok(())
}
pub fn exit(&mut self) -> Result<()> {
self.stop()?;
if crossterm::terminal::is_raw_mode_enabled()? {
self.flush()?;
if self.paste {
crossterm::execute!(std::io::stderr(), DisableBracketedPaste)?;
}
if self.mouse {
crossterm::execute!(std::io::stderr(), DisableMouseCapture)?;
}
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?;
crossterm::terminal::disable_raw_mode()?;
}
Ok(())
}
pub fn cancel(&self) {
self.cancellation_token.cancel();
}
pub fn suspend(&mut self) -> Result<()> {
self.exit()?;
#[cfg(not(windows))]
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?;
Ok(())
}
pub fn resume(&mut self) -> Result<()> {
self.enter()?;
Ok(())
}
pub async fn next(&mut self) -> Option<Event> {
self.event_rx.recv().await
}
}
impl Deref for Tui {
type Target = ratatui::Terminal<Backend<std::io::Stderr>>;
fn deref(&self) -> &Self::Target {
&self.terminal
}
}
impl DerefMut for Tui {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.terminal
}
}
impl Drop for Tui {
fn drop(&mut self) {
self.exit().unwrap();
}
}

6
ins/app/src/ui/mod.rs Normal file
View File

@ -0,0 +1,6 @@
use crate::config::Config;
pub(crate) mod status_line;
pub(crate) mod theme;
const BOTTOM_CONTEXT_LINES: usize = 2;

View File

@ -0,0 +1,95 @@
use ratatui::{
prelude::*,
style::Style,
widgets::{Paragraph, Widget},
};
use super::theme::{DARK_PURPLE, LIGHT_GRAY, LIGHT_PURPLE};
/// An optional status line
#[derive(Debug, Clone)]
pub struct StatusLine {
/// Displays the current editor mode in the status line.
mode: String,
/// The current search buffer. Shown only in search mode.
command: Option<String>,
/// The style for the content of the sidebar
style_text: Style,
/// The style for the line itself
style_line: Style,
}
impl Default for StatusLine {
/// Creates a new instance of [`StatusLine`].
///
/// This constructor initializes with default style.
fn default() -> Self {
Self {
mode: String::new(),
command: None,
style_text: Style::default().fg(LIGHT_GRAY).bg(LIGHT_PURPLE).bold(),
style_line: Style::default().fg(LIGHT_GRAY).bg(DARK_PURPLE),
}
}
}
impl StatusLine {
/// Overwrite the style for the status lines content.
///
/// This method allows you to customize the appearance of the
/// status lines content.
#[must_use]
pub fn style_text(mut self, style: Style) -> Self {
self.style_text = style;
self
}
/// Overwrite the style for the status lines.
///
/// This method allows you to customize the appearance of the
/// status line.
#[must_use]
pub fn style_line(mut self, style: Style) -> Self {
self.style_line = style;
self
}
/// Overwrite the mode content for the status line.
///
/// This method is used internally to dynamically set the editors mode.
#[must_use]
pub fn mode<S: Into<String>>(mut self, mode: S) -> Self {
self.mode = mode.into();
self
}
/// Overwrite the search content for the status line.
///
/// This method is used internally to dynamically set the editors mode.
#[must_use]
pub fn command<S: Into<String>>(mut self, search: Option<S>) -> Self {
self.command = command.map(Into::into);
self
}
}
impl Widget for StatusLine {
fn render(self, area: Rect, buf: &mut Buffer) {
// Split the layout horizontally.
let constraints = [Constraint::Length(10), Constraint::Min(0)];
let [left, right] = Layout::horizontal(constraints).areas(area);
// Build the content and block widgets
let mode_paragraph = Paragraph::new(Line::from(Span::from(self.mode)))
.alignment(Alignment::Center)
.style(self.style_text);
let command_text = self.command.map_or(String::new(), |s| format!(":{s}"));
let command_paragraph = Paragraph::new(Line::from(Span::from(command_text)))
.alignment(Alignment::Left)
.style(self.style_line);
// Determine the alignment position
mode_paragraph.render(left, buf);
command_paragraph.render(right, buf);
}
}

116
ins/app/src/ui/tab_line.rs Normal file
View File

@ -0,0 +1,116 @@
use ratatui::{
prelude::*,
style::Style,
widgets::{Paragraph, Widget},
};
use super::theme::{DARK_PURPLE, LIGHT_GRAY, LIGHT_PURPLE};
/// An optional status line
#[derive(Debug, Clone)]
pub struct TabLine {
/// Displays the current editor mode in the status line.
mode: String,
/// The current search buffer. Shown only in search mode.
search: Option<String>,
/// The style for the content of the sidebar
style_text: Style,
/// The style for the line itself
style_line: Style,
// Whether to align content to the left (true) or the right (false)
align_left: bool,
}
impl Default for StatusLine {
/// Creates a new instance of [`StatusLine`].
///
/// This constructor initializes with default style.
fn default() -> Self {
Self {
mode: String::new(),
search: None,
style_text: Style::default().fg(LIGHT_GRAY).bg(LIGHT_PURPLE).bold(),
style_line: Style::default().fg(LIGHT_GRAY).bg(DARK_PURPLE),
align_left: true,
}
}
}
impl StatusLine {
/// Overwrite the style for the status lines content.
///
/// This method allows you to customize the appearance of the
/// status lines content.
#[must_use]
pub fn style_text(mut self, style: Style) -> Self {
self.style_text = style;
self
}
/// Overwrite the style for the status lines.
///
/// This method allows you to customize the appearance of the
/// status line.
#[must_use]
pub fn style_line(mut self, style: Style) -> Self {
self.style_line = style;
self
}
/// Overwrite the mode content for the status line.
///
/// This method is used internally to dynamically set the editors mode.
#[must_use]
pub fn mode<S: Into<String>>(mut self, mode: S) -> Self {
self.mode = mode.into();
self
}
/// Overwrite the search content for the status line.
///
/// This method is used internally to dynamically set the editors mode.
#[must_use]
pub fn search<S: Into<String>>(mut self, search: Option<S>) -> Self {
self.search = search.map(Into::into);
self
}
/// Set the alignment for the status line content.
///
/// Set to true to align content to the left, false to align to the right.
#[must_use]
pub fn align_left(mut self, align_left: bool) -> Self {
self.align_left = align_left;
self
}
}
impl Widget for StatusLine {
fn render(self, area: Rect, buf: &mut Buffer) {
// Split the layout horizontally.
let constraints = if self.align_left {
[Constraint::Length(10), Constraint::Min(0)]
} else {
[Constraint::Min(0), Constraint::Length(10)]
};
let [left, right] = Layout::horizontal(constraints).areas(area);
// Build the content and block widgets
let mode_paragraph = Paragraph::new(Line::from(Span::from(self.mode)))
.alignment(Alignment::Center)
.style(self.style_text);
let search_text = self.search.map_or(String::new(), |s| format!("/{s}"));
let search_paragraph = Paragraph::new(Line::from(Span::from(search_text)))
.alignment(Alignment::Left)
.style(self.style_line);
// Determine the alignment position
if self.align_left {
mode_paragraph.render(left, buf);
search_paragraph.render(right, buf);
} else {
search_paragraph.render(left, buf);
mode_paragraph.render(right, buf);
};
}
}

18
ins/app/src/ui/theme.rs Normal file
View File

@ -0,0 +1,18 @@
use super::StatusLine;
use ratatui::style::{Color, Style};
// Tailwind slate c100
pub(crate) const LIGHT_GRAY: Color = Color::Rgb(248, 250, 252);
// Tailwind slate c50
pub(crate) const WHITE: Color = Color::Rgb(248, 250, 252);
// Tailwind slate c900
pub(crate) const DARK_BLUE: Color = Color::Rgb(15, 23, 42);
// Tailwind purple c700 & c900
pub(crate) const LIGHT_PURPLE: Color = Color::Rgb(126, 34, 206);
pub(crate) const DARK_PURPLE: Color = Color::Rgb(88, 28, 135);
// Tailwind yellow c400
pub(crate) const YELLOW: Color = Color::Rgb(250, 204, 21);