Skip to content

Commit

Permalink
make docs optional and add a navigable viewer for markdown files
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasLaPiana committed Dec 30, 2023
1 parent 98cda36 commit b079b7d
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 34 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ See [synthesizer](https://github.com/ThomasLaPiana/synthesizer) for an example o

Rox was created for the purpose of making building and developing applications easier. It is designed to focus on extensiblity, performance, and documentation. Here are a few of the key features that help Rox achieve that goal:

- __Dynamically Generated CLI__: Rox's tasks and pipelines are dynamically added as subcommands to the CLI at runtime. Configuration is handled entirely in YAML files.
- __Powerful Primitives__: Using a combination of Rox's primitives (`Tasks`, `Pipelines` and `Templates`) it is possible to handle virtually any use-case with elegance and minimal boilerplate.
- __Documentation as a First-Class Feature__: Names and descriptions are automatically injected into the CLI at runtime, so your `help` command is always accurate. This helps developers understand what various tasks and pipelines do without needing to dive into the code.
- __Performant__: Minimal overhead and native executables for a variety of architectures and operating systems.
- __Efficient__: By utilizing pipeline stages and parallel execution, developers are empowered to make use of multi-core machines to speed up build and development tasks.
- __User-Friendly__: Task results are shown to the user in an easy-to-consume table format along with useful metadata. This makes debugging easier, and shows potential bottlenecks in build steps.
- **Dynamically Generated CLI**: Rox's tasks and pipelines are dynamically added as subcommands to the CLI at runtime. Configuration is handled entirely in YAML files.
- **Primitives**: Using a combination of Rox's primitives (`Tasks`, `Pipelines` and `Templates`) it is possible to handle virtually any use-case with elegance and minimal boilerplate.
- **Documentation as a First-Class Feature**: Names and descriptions are automatically injected into the CLI at runtime, so your `help` command is always accurate. This helps developers understand what various tasks and pipelines do without needing to dive into the code.
- **Performant**: Minimal overhead and native executables for a variety of architectures and operating systems.
- **Efficient**: By utilizing pipeline stages and parallel execution, developers are empowered to make use of multi-core machines to speed up build and development tasks.
- **User-Friendly**: Task results are shown to the user in an easy-to-consume table format along with useful metadata. This makes debugging easier, and shows potential bottlenecks in build steps.

## Video Walkthrough

Expand Down
10 changes: 6 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ use clap::{crate_version, Arg, ArgAction, Command};
pub fn construct_cli(
tasks: &[Task],
pipelines: &Option<Vec<Pipeline>>,
docs: &[Docs],
docs: &Option<Vec<Docs>>,
) -> clap::Command {
let mut cli = cli_builder();

// Docs
let docs_subcommands = build_docs_subcommands(docs);
cli = cli.subcommands(vec![docs_subcommands]);
if let Some(docs) = docs {
let docs_subcommands = build_docs_subcommands(docs);
cli = cli.subcommands(vec![docs_subcommands]);
}

// Tasks
let task_subcommands = build_task_subcommands(tasks);
Expand Down Expand Up @@ -57,7 +59,7 @@ pub fn cli_builder() -> Command {
pub fn build_docs_subcommands(docs: &[Docs]) -> Command {
let subcommands: Vec<Command> = docs
.iter()
.map(|doc| Command::new(&doc.name).about(doc.description.clone()))
.map(|doc| Command::new(&doc.name).about(doc.description.clone().unwrap_or_default()))
.collect();

Command::new("docs")
Expand Down
108 changes: 108 additions & 0 deletions src/docs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use crate::models::{Docs, DocsKind};
use std::io::{stdout, Write};
use termimad::crossterm::{
cursor::{Hide, Show},
event::{self, Event, KeyCode, KeyEvent},
queue,
style::Color::*,
terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
};
use termimad::MadSkin;
use termimad::*;

static KEYBINDINGS: &str = r#"
# Navigation Keybindings
| Key(s) | Action |
| :---: | :------: |
| q/Esc | Exit |
| k/Up | Scroll up one line|
| j/Down | Scroll down one line |
| g | Jump to top |
| G | Jump to Bottom |
| u | Page Up |
| d | Page Down |
------
"#;

pub fn display_docs(docs: &Docs) {
match docs.kind {
DocsKind::Markdown => {
let markdown = std::fs::read_to_string(&docs.path).unwrap();
run_app(&markdown).unwrap();
}
DocsKind::Text => {
let contents = std::fs::read_to_string(&docs.path).unwrap();
println!("{}", contents);
}
DocsKind::URL => {
println!("> Opening '{}' in your browser...", docs.path);
webbrowser::open(&docs.path).unwrap()
}
}
}

/// Build and Run the terminal application
/// Taken from -> https://github.com/Canop/termimad/blob/main/examples/scrollable/main.rs
fn run_app(docs: &str) -> Result<(), Error> {
let mut w = stdout(); // we could also have used stderr
let skin = make_skin();
queue!(w, EnterAlternateScreen)?;
terminal::enable_raw_mode()?;
queue!(w, Hide)?; // hiding the cursor

let markdown = format!("{}\n{}", KEYBINDINGS, docs);
// Handle the document
let mut view = MadView::from(markdown, view_area(), skin);
loop {
view.write_on(&mut w)?;
w.flush()?;
match event::read() {
Ok(Event::Key(KeyEvent { code, .. })) => match code {
// Negative number is up, Positive number is down
KeyCode::Up => view.try_scroll_lines(-1),
KeyCode::Down => view.try_scroll_lines(1),
KeyCode::Char('k') => view.try_scroll_lines(-1),
KeyCode::Char('j') => view.try_scroll_lines(1),

KeyCode::Char('u') => view.try_scroll_lines(-30),
KeyCode::Char('d') => view.try_scroll_lines(30),

KeyCode::Char('g') => view.try_scroll_lines(-100000),
KeyCode::Char('G') => view.try_scroll_lines(100000),

KeyCode::Char('q') => break,
KeyCode::Esc => break,
_ => continue,
},
Ok(Event::Resize(..)) => {
queue!(w, Clear(ClearType::All))?;
view.resize(&view_area());
}
_ => {}
}
}
terminal::disable_raw_mode()?;
queue!(w, Show)?; // we must restore the cursor
queue!(w, LeaveAlternateScreen)?;
w.flush()?;
Ok(())
}

fn make_skin() -> MadSkin {
let mut skin = MadSkin::default();
skin.table.align = Alignment::Center;
skin.set_headers_fg(AnsiValue(178));
skin.bold.set_fg(Yellow);
skin.italic.set_fg(Magenta);
skin.scrollbar.thumb.set_fg(AnsiValue(178));
skin.code_block.align = Alignment::Center;
skin
}

fn view_area() -> Area {
let mut area = Area::full_screen();
area.pad_for_max_width(120); // we don't want a too wide text column
area
}
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod cli;
mod docs;
mod execution;
mod model_injection;
pub mod models;
Expand Down Expand Up @@ -78,9 +79,11 @@ pub fn rox() -> RoxResult<()> {
let results: Vec<Vec<TaskResult>> = match cli_matches.subcommand_name().unwrap() {
"docs" => {
let docs_map: HashMap<String, models::Docs> = std::collections::HashMap::from_iter(
docs.into_iter().map(|doc| (doc.name.to_owned(), doc)),
docs.into_iter()
.flatten()
.map(|doc| (doc.name.to_owned(), doc)),
);
output::display_docs(docs_map.get(subcommand_name).unwrap());
docs::display_docs(docs_map.get(subcommand_name).unwrap());
std::process::exit(0);
}
"logs" => {
Expand Down
4 changes: 2 additions & 2 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub enum DocsKind {
#[derive(Deserialize, Debug, Clone)]
pub struct Docs {
pub name: String,
pub description: String,
pub description: Option<String>,
pub kind: DocsKind,
pub path: String,
}
Expand Down Expand Up @@ -178,7 +178,7 @@ pub struct Pipeline {
#[derive(Deserialize, Debug, Default, Clone)]
#[serde(deny_unknown_fields)]
pub struct RoxFile {
pub docs: Vec<Docs>,
pub docs: Option<Vec<Docs>>,
pub tasks: Vec<Task>,
pub pipelines: Option<Vec<Pipeline>>,
pub templates: Option<Vec<Template>>,
Expand Down
21 changes: 1 addition & 20 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::models::{AllResults, Docs, DocsKind, PassFail};
use crate::models::{AllResults, PassFail};
use cli_table::{format::Justify, print_stdout, Cell, Style, Table};
use colored::Colorize;
use termimad::MadSkin;

const LOG_DIR: &str = ".rox";

Expand Down Expand Up @@ -82,21 +81,3 @@ pub fn display_execution_results(results: &AllResults) {
)
.is_ok());
}

pub fn display_docs(docs: &Docs) {
match docs.kind {
DocsKind::Markdown => {
let markdown = std::fs::read_to_string(&docs.path).unwrap();
let skin = MadSkin::default();
println!("{}", skin.term_text(&markdown));
}
DocsKind::Text => {
let contents = std::fs::read_to_string(&docs.path).unwrap();
println!("{}", contents);
}
DocsKind::URL => {
println!("> Opening '{}' in your browser...", docs.path);
webbrowser::open(&docs.path).unwrap()
}
}
}

0 comments on commit b079b7d

Please sign in to comment.