duckyscript 0.1.0

parser for duckyscript
Documentation
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release

use std::path::PathBuf;

use eframe::egui;
use egui::{TextEdit, Vec2};

use crate::parser::{get_duckyscript_tokens, DuckyScript, DuckyScriptError};
use crate::vm::DuckyScriptVm;

enum ScriptState {
    Loaded(DuckyScript),
    Error(String),
}

#[derive(Default, Clone, Copy, PartialEq, Eq)]
enum Page {
    #[default]
    VmRun,
    RemoteRun
}
#[derive(Default)]
pub struct DuckyScriptGui {
    script_path: std::path::PathBuf,
    code: String,
    parsed_script: Option<ScriptState>,
    page: Page,
}

impl eframe::App for DuckyScriptGui {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.horizontal(|ui| {
                self.file_selector(ui);
            });

            ui.separator();
            self.error(ctx, ui);

            let tabs = [
                ("test-run", Page::VmRun),
                ("run remotely", Page::RemoteRun),
            ];

            ui.horizontal(|ui| {
                for ( label, p) in tabs.iter() {
                    if ui
                        .add_enabled(*p != self.page, egui::Button::new(*label))
                        .clicked()
                    {
                        self.page = *p;
                    }
                }
            });

            ui.separator();
            match self.page {
                Page::VmRun => {
                    self.test_run(ctx, ui);
                },
                Page::RemoteRun => {
                    self.remote_run(ctx, ui);
                }
            }
        });
    }
}

impl DuckyScriptGui {
    
    fn test_run(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
        ui.label("Run script in vm without executng any keyboard events");
    }
    fn remote_run(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
        ui.label("Run on a remote device");
    }

    fn error(&mut self, ctx: &egui::Context, _: &mut egui::Ui) {
        let mut clear_script = false;
        if let Some(ScriptState::Error(e)) = &self.parsed_script {
            egui::Window::new("Parse Error").show(ctx, |ui| {
                ui.text_edit_multiline(&mut e.as_ref());
                clear_script = ui.button("Close").clicked();
            });
        }
        if clear_script {
            self.parsed_script = None;
        }
    }
    fn file_selector(&mut self, ui: &mut egui::Ui) {
        ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
            if ui.button("Open file…").clicked() {
                if let Some(path) = rfd::FileDialog::new().pick_file() {
                    self.load_script( path);
                }
            }
            ui.add_sized(
                ui.available_size(),
                TextEdit::singleline(&mut self.script_path.to_str().unwrap_or(""))
                    .hint_text("Select a ducky script"),
            );
        });
    }

    fn load_script(&mut self, path: PathBuf) {
        let code = match std::fs::read_to_string(&path) {
            Ok( c) => c,
            Err(e) => {
                self.parsed_script = Some(ScriptState::Error(e.to_string()));
                return;
            }
        };
        match get_duckyscript_tokens(&code) {
            Ok(tokens) => {
                self.script_path = path;

                self.parsed_script = Some(ScriptState::Loaded(DuckyScript::parse_tokens(tokens)));
                self.code = code;
            }
            Err(e) => {
                self.parsed_script = Some(ScriptState::Error(e.to_string()));
            }
        }
    }
}