Crate ltrait

Source
Expand description

§Base concepts

Note: using ChatGPT to translate to English(About 70% of all?).

§Extensions

Each type of extension is defined by a relatively simple trait.

NameDescription
SourceIn terms of type, it is a Stream<Item = Item>. It is a data source.
GeneratorIt is similar to Source, but it takes an input and generates an arbitrary number of Items from it.
FilterIt takes one Item (also called Context) along with an input from the user, and applies a predicate to decide whether to keep it.
SorterIt takes two Items and an input, and compares the Items with each other.
UIIt takes input from the user, processes it and then displays it on the screen.
ActionIt takes the selected Item and executes the Action.

§Diagram

image

§Making the first launcher

Up to now, we have briefly explained the different types of extensions, but from here, we’ll create a simple Launcher as a tutorial. For a practical configuration, please refer to the author’s settings.

On a side note: Even though it’s called a Launcher, it can be used for purposes other than launching, so the name might be somewhat misleading.

§Create project

Create a binary crate with cargo. And add ltrait and tokio as a dependency.

cargo new hello-ltrait
cd hello-ltrait
cargo add ltrait
cargo add tokio --features=full

Configure error handler and logger:

use ltrait::color_eyre::Result;
use ltrait::{Launcher, Level};

#[tokio::main]
async fn main() -> Result<()> {
    // keeping _guard is required to write log
    let _guard = ltrait::setup(Level::INFO)?;
    // TODO: Configure and run Launcher

    Ok(())
}

§Create a launcher and set UI

I introduce the concept of Cusion. Cusion is a type, and it is recommended to implement it using an enum. Although it is not impossible to create a Launcher without Cusion, it is not recommended due to the significant limitations it imposes. Transform the Items extracted from the Source or Generator into a Cusion. Then, from the Cusion, transform it into the Context for each Sorter, Filter, etc.

I recommend you to use ltrait-ui-tui as your first ui. Add as a dependency.

cargo add ltrait-ui-tui

And write main.rs.

use ltrait::color_eyre::Result;
use ltrait::{Launcher, Level};

use ltrait_ui_tui::{Tui, TuiConfig, TuiEntry, style::Style, Viewport};

enum Item {
    // TODO: add source
}


impl Into<String> for &Item {
    fn into(self) -> String {
        match self {
            // TODO:
            _ => "unknown item".into()
        }
    }
}


#[tokio::main]
async fn main() -> Result<()> {
    // keeping _guard is required to write log
    let _guard = ltrait::setup(Level::INFO)?;

    let launcher = Launcher::default()
        .set_ui(
            Tui::new(TuiConfig::new(
                Viewport::Fullscreen,
                '>', // Selected
                ' ',
                ltrait_ui_tui::sample_keyconfig,
            )),
            |c: &Item| TuiEntry {
                text: (c.into(), Style::new()),
            },
        );

    launcher.run().await
}

Since there is not even a single Source or Generator here, running it should result in just an input field. Let’s try running it.

cargo run

§Add a source, filter, sorter

The simplest source can be created by crate::source::from_iter. You can add a source by crate::launcher::Launcher::add_source.

If you only want to see even numbers, this can easily be achieved using crate::filter::ClosureFilter and crate::launcher::Launcher::add_raw_filter. And if you want to see the scene in order, you can use crate::sorter::ClosureSorter and crate::launcher::Launcher::add_raw_sorter.

Performance can be optimised by setting the crate::launcher::Launcher::batch_size.

Note: add_raw_** works like a syntax sugar. add_raw_**(/* ... */) will like add_**(/* ... */, |c| c). The behaviour changes a little due to lifetime and other factors, so it is recommended to use raw if a raw version can be used.

use ltrait::color_eyre::Result;
use ltrait::{
    Launcher,
    Level,
    filter::ClosureFilter,
    sorter::ClosureSorter,
};

use ltrait_ui_tui::{Tui, TuiConfig, TuiEntry, style::Style, Viewport};

use std::cmp;

enum Item {
    Num(u32)
}


impl Into<String> for &Item {
    fn into(self) -> String {
        match self {
            Item::Num(x) => format!("{x}"),
            _ => "unknown item".into()
        }
    }
}


#[tokio::main]
async fn main() -> Result<()> {
    // keeping _guard is required to write log
    let _guard = ltrait::setup(Level::INFO)?;

    let launcher = Launcher::default()
        // the simplest source
        .add_source(ltrait::source::from_iter(1..=5000), /* transformer */ Item::Num)
        .add_raw_filter(ClosureFilter::new(|c, _ /* input */| {
            match c {
                Item::Num(x) => (x % 2) == 0,
                _ => true, // If variants are added to Item in the future, they are ignored here
            }
        }))
        .reverse_sorter(false)
        .add_raw_sorter(ClosureSorter::new(|lhs, rhs, _| {
            match (lhs, rhs) {
                (Item::Num(lhs), Item::Num(rhs)) => lhs.cmp(rhs),
                _ => cmp::Ordering::Equal
            }
        }))
        .batch_size(500)
        .set_ui(
            Tui::new(TuiConfig::new(
                Viewport::Fullscreen,
                '>', // Selected
                ' ',
            )),
            |c| TuiEntry {
                text: (c.into(), Style::new()),
            },
        );

    launcher.run().await
}

Let’s run it again.

cargo run

🎉 It’s complete! 🎉

Re-exports§

pub use crate::action::Action;
pub use crate::filter::Filter;
pub use crate::generator::Generator;
pub use crate::launcher::Launcher;
pub use crate::sorter::Sorter;
pub use crate::source::Source;
pub use crate::ui::UI;
pub use async_trait;
pub use color_eyre;
pub use tokio_stream;

Modules§

action
filter
generator
launcher
sorter
source
ui

Structs§

Level
Describes the level of verbosity of a span or event.

Functions§

setup
Install color_eyre and setup tracing(with tracing-log)