#progress-bar #output #event-listener #user #events #event-handling #command-output

storyteller

专注于支持面向用户输出的库,同时支持多种输出类型(例如 json,进度条等)

14 个版本 (1 个稳定版本)

1.0.0 2024年7月24日
0.8.1 2023年6月5日
0.8.0 2022年9月28日
0.6.1 2022年6月19日

#551 in 解析器实现

Download history 298/week @ 2024-04-23 150/week @ 2024-04-30 340/week @ 2024-05-07 198/week @ 2024-05-14 159/week @ 2024-05-21 273/week @ 2024-05-28 209/week @ 2024-06-04 135/week @ 2024-06-11 268/week @ 2024-06-18 376/week @ 2024-06-25 158/week @ 2024-07-02 221/week @ 2024-07-09 185/week @ 2024-07-16 398/week @ 2024-07-23 232/week @ 2024-07-30 538/week @ 2024-08-06

1,368 每月下载次数

Apache-2.0 OR MIT

100KB
321 代码行

🎙 Storyteller

用户输出处理的库

目录

简介

此库旨在由具有多个用户界面选项的工具使用,通过这些选项它们可以通信,同时也有各种单独的命令(或流程),每个命令都需要仔细指定自己的输出格式。

该库由三个主要构建块组成,并在这些构建块之上提供默认实现。它帮助您设置程序架构

三个构建块是

  • EventHandler:处理用户输出的事件处理器,例如

    • 将事件格式化为 json 行并打印到 stderr 的处理器
    • 更新进度条的处理器
    • 收集事件的软件测试处理器
    • 为每个事件发送 websocket 消息的处理器
    • 更新用户界面的处理器
  • EventReporter:在程序逻辑期间调用。用于将用户输出通信给用户。在程序逻辑期间,通过事件调用报告器,因此您不需要在程序流程的中间处理格式化和显示细节。

  • EventListener:接收由报告器发送的事件,并运行 EventHandler。通常在单独的线程中启动,以便不会阻塞。

在这些构建块之上,提供了一个基于通道的实现,该实现将 EventHandler 在单独的线程中运行。要使用此实现,请参阅 ChannelReporterChannelEventListener 的文档。

除了这些提供的元素外,您还需要

  • 定义一个可以被用作事件的类型
  • 定义一个或多个事件处理器(例如:impl EventHandler<Event = YourEventType>)。

可视化介绍

点击这里查看大图。 可视化介绍草图亮色 svg暗色 svg亮色 png暗色 png

示例

实际应用示例

Storyteller自从v0.16版本以来就被cargo-msrv所使用。要预览如何指定事件,您可以点击事件模块。在处理器模块中,可以找到多个处理器,例如写入JSON的处理器、打印美观的、易于阅读的输出的处理器、打印用于shell命令的最小最终结果输出的处理器、丢弃输出的处理器以及用于集成测试的处理器。

一窥storyteller的滋味

use std::cell::RefCell;
use std::hash::Hasher;
use std::io::{Stderr, Write};
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::{io, thread};
use storyteller::{EventHandler, FinishProcessing};

use storyteller::{
    event_channel, ChannelEventListener, ChannelReporter, EventListener, EventReporter,
};

#[derive(serde::Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum Event {
    DiceThrow { throw: u8 },
    YouWin,
    YouLose,
}

#[derive(Default)]
struct JsonHandler;

impl EventHandler for JsonHandler {
    type Event = Event;

    fn handle(&self, event: Self::Event) {
        let serialized_event = serde_json::to_string(&event).unwrap();

        eprintln!("{}", serialized_event);
    }
}

// See the test function `bar` in src/tests.rs for an example where the handler is a progress bar.
fn main() {
    let (sender, receiver) = event_channel::<Event>();

    // Handlers are implemented by you. Here you find one which writes jsonlines messages to stderr.
    // This can be anything, for example a progress bar (see src/tests.rs for an example of this),
    // a fake reporter which collects events for testing or maybe even a "MultiHandler<'h>" which
    // consists of a Vec<&'h dyn EventHandler> and executes multiple handlers under the hood.
    let handler = JsonHandler::default();

    // This one is included with the library. It just needs to be hooked up with a channel.
    let reporter = ChannelReporter::new(sender);

    // This one is also included with the library. It also needs to be hooked up with a channel.
    let listener = ChannelEventListener::new(receiver);

    // Here we use the JsonHandler we defined above, in combination with the default `EventListener`
    // and  `ChannelEventListener` defined above.
    //
    // If we don't run the handler, we'll end up in an infinite loop, because our `reporter.disconnect()`
    // below will block until it receives a Disconnect message.
    let fin = listener.run_handler(Arc::new(handler));

    // Now onto this program, let's play a game of dice!
    for _ in 0..100 {
        let dice = roll_dice();
        reporter
            .report_event(Event::DiceThrow { throw: dice })
            .unwrap();

        if dice >= 3 {
            reporter.report_event(Event::YouWin).unwrap();
        } else {
            reporter.report_event(Event::YouLose).unwrap();
        }

        thread::sleep(Duration::from_millis(100))
    }

    // Within the ChannelReporter, the sender is dropped, thereby disconnecting the channel
    // Already sent events can still be processed.
    let _ = reporter.disconnect();

    // To keep the processing of already sent events alive, we block the handler
    let _ = fin.finish_processing();
}

static SEED: AtomicU32 = AtomicU32::new(1);

fn roll_dice() -> u8 {
    let mut random = SEED.load(Ordering::SeqCst);
    random ^= random << 13;
    random ^= random >> 17;
    random ^= random << 5;
    SEED.store(random, Ordering::SeqCst);

    (random % 6 + 1) as u8
}

起源

这个库是基于早期的实验的改进实现。它的目的是被cargo-msrv使用,因为这个项目已经超越了其当前的用户输出实现。

贡献

欢迎贡献、反馈或其他联系!请随意发送消息或创建一个问题 😄。

许可证

根据以下任一许可证授权

任选其一。

贡献

除非您明确表示,否则任何有意提交以包含在您的工作中的贡献,根据Apache-2.0许可证的定义,应双授权如上所述,不附加任何额外的条款或条件。

依赖