Skip to content

Commit

Permalink
feat: add multi visualization (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
guilhermeprokisch authored Sep 13, 2024
1 parent 7fc203b commit e34ecb8
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 57 deletions.
9 changes: 8 additions & 1 deletion docs/main.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ If FILE is not provided, see reads from standard input.
| `--render-links` | Enable or disable clickable links |
| `--render-table-borders` | Enable or disable table borders in rendered output |
| `--show-line-numbers` | Show or hide line numbers when rendering code files |
| `--show-filename` | Show or hide the filename before rendering content |
| `--config <file>` | Specify a custom configuration file |
| `--use-color` | Control color output |

Expand Down Expand Up @@ -78,5 +79,11 @@ see --render-table-borders=true path/to/your/markdown_file.md
Disable color output when piping to another command:

```bash
see --use-color=false path/to/your/markdown_file.rs | echo
see --use-color=false path/to/your/markdown_file.rs | less
```

Render content without showing the filename:

```bash
see --show-filename=false path/to/your/markdown_file.md
```
55 changes: 32 additions & 23 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct AppConfig {
pub render_links: bool,
pub render_table_borders: bool,
pub show_line_numbers: bool,
pub show_filename: bool,
pub debug_mode: bool,
pub use_colors: bool,
}
Expand Down Expand Up @@ -55,6 +56,7 @@ impl AppConfig {
render_links: true,
render_table_borders: false,
show_line_numbers: true,
show_filename: false,
debug_mode: false,
use_colors: true,
}
Expand Down Expand Up @@ -82,8 +84,8 @@ pub fn get_config() -> &'static AppConfig {
CONFIG.get().expect("Config not initialized")
}

pub fn initialize_app() -> io::Result<(AppConfig, Option<PathBuf>)> {
let (mut config, file_path) = parse_cli_args()?;
pub fn initialize_app() -> io::Result<(AppConfig, Option<Vec<PathBuf>>)> {
let (mut config, file_paths) = parse_cli_args()?;

if !std::io::stdout().is_terminal() {
config.use_colors = false;
Expand All @@ -98,28 +100,13 @@ pub fn initialize_app() -> io::Result<(AppConfig, Option<PathBuf>)> {
.set(config.clone())
.map_err(|_| io::Error::new(io::ErrorKind::AlreadyExists, "Config already initialized"))?;

Ok((config, file_path))
Ok((config, file_paths))
}

fn parse_bool(value: Option<&str>) -> bool {
match value {
Some(v) => match v.to_lowercase().as_str() {
"true" | "1" => true,
"false" | "0" => false,
_ => true, // Default to true if the value is not recognized
},
None => true, // Default to true if no value is provided
}
}

fn parse_u32(value: Option<&str>) -> Option<u32> {
value.and_then(|v| v.parse().ok())
}

fn parse_cli_args() -> io::Result<(AppConfig, Option<PathBuf>)> {
fn parse_cli_args() -> io::Result<(AppConfig, Option<Vec<PathBuf>>)> {
let args: Vec<String> = env::args().collect();
let mut config = AppConfig::default();
let mut file_path = None;
let mut file_paths = Vec::new();
let mut i = 1;

while i < args.len() {
Expand All @@ -132,12 +119,13 @@ fn parse_cli_args() -> io::Result<(AppConfig, Option<PathBuf>)> {
"max-image-height" => config.max_image_height = parse_u32(parts.get(1).map(|s| *s)),
"render-images" => config.render_images = parse_bool(parts.get(1).map(|s| *s)),
"render-links" => config.render_links = parse_bool(parts.get(1).map(|s| *s)),
"render-table_borders" => {
"render-table-borders" => {
config.render_table_borders = parse_bool(parts.get(1).map(|s| *s))
}
"show-line-numbers" => {
config.show_line_numbers = parse_bool(parts.get(1).map(|s| *s))
}
"show-filename" => config.show_filename = parse_bool(parts.get(1).map(|s| *s)),
"use-colors" => config.use_colors = parse_bool(parts.get(1).map(|s| *s)),
"config" => {
if let Some(path) = parts.get(1) {
Expand Down Expand Up @@ -171,12 +159,33 @@ fn parse_cli_args() -> io::Result<(AppConfig, Option<PathBuf>)> {
}
}
} else {
file_path = Some(PathBuf::from(arg));
file_paths.push(PathBuf::from(arg));
}
i += 1;
}

Ok((config, file_path))
let file_paths = if file_paths.is_empty() {
None
} else {
Some(file_paths)
};

Ok((config, file_paths))
}

fn parse_bool(value: Option<&str>) -> bool {
match value {
Some(v) => match v.to_lowercase().as_str() {
"true" | "1" => true,
"false" | "0" => false,
_ => true, // Default to true if the value is not recognized
},
None => true, // Default to true if no value is provided
}
}

fn parse_u32(value: Option<&str>) -> Option<u32> {
value.and_then(|v| v.parse().ok())
}

fn render_help() -> io::Result<()> {
Expand Down
65 changes: 32 additions & 33 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::config::initialize_app;
use crate::render::{render_code_file, render_image_file, render_markdown};
use crate::utils::detect_language;
use crate::viewers::{determine_viewer, ViewerManager};
use std::path::Path;

mod app;
Expand All @@ -9,48 +8,48 @@ mod constants;
mod directory_tree;
mod render;
mod utils;
mod viewers;

fn main() -> std::io::Result<()> {
let (config, file_path) = initialize_app()?;

let (config, file_paths) = initialize_app()?;
if config.debug_mode {
eprintln!("Debug mode enabled");
eprintln!("Configuration: {:?}", config);
}

if let Some(path) = file_path {
let path = Path::new(&path);

if path.is_dir() {
// Handle directory
directory_tree::handle_directory(path)?;
} else {
let extension = path
.extension()
.and_then(std::ffi::OsStr::to_str)
.unwrap_or("");
let viewer_manager = ViewerManager::new();

match extension.to_lowercase().as_str() {
"md" => {
let content = app::read_content(Some(path.to_str().unwrap().to_string()))?;
let json = app::parse_and_process_markdown(&content)?;
render_markdown(&json)?;
}
"jpg" | "jpeg" | "png" | "gif" | "bmp" => {
render_image_file(path.to_str().unwrap())?;
}
_ => {
let content = app::read_content(Some(path.to_str().unwrap().to_string()))?;
let language = detect_language(path.to_str().unwrap());
render_code_file(&content, &language)?;
match file_paths {
Some(paths) => {
for path in paths {
let path = Path::new(&path);
if path.is_dir() {
directory_tree::handle_directory(path)?;
} else {
let viewer = determine_viewer(path);
if viewer.contains(&"image".to_string()) {
viewer_manager.visualize(&viewer, "", path.to_str())?;
} else {
match app::read_content(Some(path.to_str().unwrap().to_string())) {
Ok(content) => {
viewer_manager.visualize(&viewer, &content, path.to_str())?;
}
Err(e) => {
eprintln!("Error reading file {}: {}", path.display(), e);
}
}
}
}
}
}
} else {
// Handle stdin input (assuming it's always Markdown)
let content = app::read_content(None)?;
let json = app::parse_and_process_markdown(&content)?;
render_markdown(&json)?;
None => {
let content = app::read_content(None)?;
viewer_manager.visualize(
&["markdown".to_string(), "code".to_string()],
&content,
None,
)?;
}
}

Ok(())
Expand Down
123 changes: 123 additions & 0 deletions src/viewers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use crate::app;
use crate::config::get_config;
use crate::render::{render_code_file, render_image_file, render_markdown};
use crate::utils::detect_language;
use devicons::{icon_for_file, File, Theme};
use std::collections::HashMap;
use std::io::{self, Write};
use std::path::Path;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

pub struct ViewerManager {
viewers: HashMap<String, Box<dyn Viewer>>,
}

impl ViewerManager {
pub fn new() -> Self {
let mut viewer_manager = ViewerManager {
viewers: HashMap::new(),
};
// Register default viewers
viewer_manager.register_viewer("markdown", Box::new(MarkdownViewer));
viewer_manager.register_viewer("code", Box::new(CodeViewer));
viewer_manager.register_viewer("image", Box::new(ImageViewer));
viewer_manager
}

pub fn register_viewer(&mut self, name: &str, viewer: Box<dyn Viewer>) {
self.viewers.insert(name.to_string(), viewer);
}

pub fn visualize(
&self,
viewer_names: &[String],
content: &str,
file_path: Option<&str>,
) -> io::Result<()> {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
let config = get_config();

// Display file name if available and show_filename is true
if config.show_filename {
if let Some(path) = file_path {
let file_name = Path::new(path)
.file_name()
.unwrap_or_default()
.to_string_lossy();

let file = File::new(Path::new(path));
let icon = icon_for_file(&file, Some(Theme::Dark));
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)).set_bold(true))?;
writeln!(stdout)?;
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
writeln!(stdout, "{} {}", icon.icon, file_name)?;
stdout.reset()?;
writeln!(stdout)?;
}
}

for (index, viewer_name) in viewer_names.iter().enumerate() {
if index > 0 {
writeln!(stdout)?;
}
if let Some(viewer) = self.viewers.get(viewer_name) {
viewer.visualize(content, file_path)?;
} else {
eprintln!("Unknown viewer: {}", viewer_name);
}
}
Ok(())
}
}

pub trait Viewer {
fn visualize(&self, content: &str, file_path: Option<&str>) -> io::Result<()>;
}

struct MarkdownViewer;

impl Viewer for MarkdownViewer {
fn visualize(&self, content: &str, _file_path: Option<&str>) -> io::Result<()> {
let json = app::parse_and_process_markdown(content)?;
render_markdown(&json)
}
}

struct CodeViewer;

impl Viewer for CodeViewer {
fn visualize(&self, content: &str, file_path: Option<&str>) -> io::Result<()> {
let language = file_path
.map(|path| detect_language(path))
.unwrap_or_else(|| "txt".to_string());
render_code_file(content, &language)
}
}

struct ImageViewer;

impl Viewer for ImageViewer {
fn visualize(&self, _content: &str, file_path: Option<&str>) -> io::Result<()> {
if let Some(path) = file_path {
render_image_file(path)
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"No file path provided for image rendering",
))
}
}
}

pub fn determine_viewer(file_path: &Path) -> Vec<String> {
let extension = file_path
.extension()
.and_then(std::ffi::OsStr::to_str)
.unwrap_or("")
.to_lowercase();
match extension.as_str() {
"md" => vec!["markdown".to_string()],
"jpg" | "jpeg" | "png" | "gif" | "bmp" | "webp" => vec!["image".to_string()],
_ => vec!["code".to_string()],
}
}

0 comments on commit e34ecb8

Please # to comment.