Skip to content

Commit

Permalink
Add describe command (#273)
Browse files Browse the repository at this point in the history
* feat: add name to code unit detectors

* feat: add describe

* feat: detect parent crate of rust source file
  • Loading branch information
Jujulego authored Jan 5, 2025
1 parent 0bd7bf1 commit 3bc4acb
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 55 deletions.
43 changes: 43 additions & 0 deletions crates/ring-cli/src/commands/describe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::core::RingCore;
use clap::{arg, value_parser, ArgMatches, Command};
use crossterm::style::Stylize;
use std::env;
use std::path::PathBuf;
use itertools::Itertools;
use tracing::instrument;
use ring_tag::Tag;

pub fn build_command() -> Command {
Command::new("describe")
.aliases(["d", "desc"])
.arg(arg!([path])
.value_parser(value_parser!(PathBuf)))
}

#[instrument(name = "cli.describe", skip(core, args))]
pub fn handle_command(core: &RingCore, args: &ArgMatches) -> anyhow::Result<()> {
// Extract arguments
let current_dir = env::current_dir()?;
let path = args.get_one::<PathBuf>("path")
.unwrap_or(&current_dir);

for detector in core.code_unit_detectors() {
if let Some(unit) = detector.detect(path)? {
println!("{} detected by {}",
unit.name().map(Stylize::bold).unwrap_or("unknown".dark_grey()),
detector.name());

if let Some(parent) = unit.parent() {
println!("\u{2570}\u{2574}{} at {}",
parent.name().map(Stylize::bold).unwrap_or("unknown".dark_grey()),
parent.path().display());
}

println!("language: {}", Tag::from(unit.language()).stylize());
println!("tags: {}", unit.tags().iter().map(Tag::stylize_full).join(" "));
println!();
}
}

Ok(())
}
3 changes: 2 additions & 1 deletion crates/ring-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod describe;
pub mod list;
pub mod processes;
pub mod processes;
2 changes: 2 additions & 0 deletions crates/ring-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn main() -> anyhow::Result<()> {
.propagate_version(true)
.subcommand_required(true)
.subcommands([
commands::describe::build_command(),
commands::list::build_command(),
commands::processes::build_command(),
])
Expand All @@ -32,6 +33,7 @@ fn main() -> anyhow::Result<()> {
let core = RingCore::new();

match args.subcommand() {
Some(("describe", args)) => commands::describe::handle_command(&core, args),
Some(("list", args)) => commands::list::handle_command(&core, args),
Some(("processes", args)) => commands::processes::handle_command(&core, args),
_ => unreachable!()
Expand Down
31 changes: 22 additions & 9 deletions crates/ring-color/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[cfg(test)]
mod mock_supports_color;

use std::fmt::Display;
use rgb::RGB;

#[cfg(not(test))]
Expand Down Expand Up @@ -76,26 +77,38 @@ impl StableColor {

#[cfg(feature = "crossterm")]
impl StableColor {
pub fn stylize<T: crossterm::style::Stylize>(&self, val: T) -> T::Styled {
pub fn stylize<T: Display>(&self, val: T) -> crossterm::style::StyledContent<T> {
self.stylize_for(val, supports_color::Stream::Stdout)
}

pub fn stylize_for<T: crossterm::style::Stylize>(&self, val: T, stream: supports_color::Stream) -> T::Styled {

pub fn stylize_for<T: Display>(&self, val: T, stream: supports_color::Stream) -> crossterm::style::StyledContent<T> {
self.style_for(stream).apply(val)
}

pub fn style<T: Display>(&self) -> crossterm::style::ContentStyle {
self.style_for(supports_color::Stream::Stdout)
}

pub fn style_for(&self, stream: supports_color::Stream) -> crossterm::style::ContentStyle {
let mut style = crossterm::style::ContentStyle::new();

if let Some(support) = supports_color_on(stream) {
if support.has_16m {
val.with(crossterm::style::Color::Rgb {
style.foreground_color = Some(crossterm::style::Color::Rgb {
r: self.rgb().r,
g: self.rgb().g,
b: self.rgb().b,
})
});
} else if support.has_256 {
val.with(crossterm::style::Color::AnsiValue(ansi_colours::ansi256_from_rgb(self.rgb)))
style.foreground_color = Some(crossterm::style::Color::AnsiValue(
ansi_colours::ansi256_from_rgb(self.rgb)
));
} else {
val.with(self.label.into())
style.foreground_color = Some(self.label.into())
}
} else {
val.stylize()
}

style
}
}

Expand Down
32 changes: 27 additions & 5 deletions crates/ring-core/src/code_unit.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::CodeLanguage;
use ring_tag::Tagged;
use std::path::Path;
use std::rc::Rc;
use ring_tag::Tagged;
use crate::CodeLanguage;

////////////////////////////////////////////////////////////////////////////////
// Code Unit
Expand Down Expand Up @@ -32,18 +32,40 @@ pub trait CodeUnit: Tagged {
////////////////////////////////////////////////////////////////////////////////

pub trait CodeUnitDetector {
fn name(&self) -> &str;

fn detect(&self, path: &Path) -> anyhow::Result<Option<Rc<dyn CodeUnit>>>;
}

////////////////////////////////////////////////////////////////////////////////
// Combined Code Unit Detector
////////////////////////////////////////////////////////////////////////////////

pub type CombinedCodeUnitDetector<const N: usize> = [Rc<dyn CodeUnitDetector>; N];
pub struct CombinedCodeUnitDetector {
name: String,
detectors: Vec<Rc<dyn CodeUnitDetector>>,
}

impl CombinedCodeUnitDetector {
pub fn new(name: String, detectors: &[Rc<dyn CodeUnitDetector>]) -> CombinedCodeUnitDetector {
CombinedCodeUnitDetector {
name,
detectors: Vec::from(detectors),
}
}

pub fn detectors(&self) -> &[Rc<dyn CodeUnitDetector>] {
&self.detectors
}
}

impl<const N: usize> CodeUnitDetector for CombinedCodeUnitDetector<N> {
impl CodeUnitDetector for CombinedCodeUnitDetector {
fn name(&self) -> &str {
self.name.as_str()
}

fn detect(&self, path: &Path) -> anyhow::Result<Option<Rc<dyn CodeUnit>>> {
for detector in self {
for detector in &self.detectors {
if let Some(unit) = detector.detect(path)? {
return Ok(Some(unit));
}
Expand Down
11 changes: 11 additions & 0 deletions crates/ring-rust/src/crates/cargo_crate_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,20 @@ impl CargoCrateDetector {

Ok(crt)
}

/// Detect if given path is within a crate, and return it
pub fn search_crate(&self, path: &Path) -> anyhow::Result<Option<Rc<CargoCrate>>> {
path.ancestors()
.find_map(|path| self.detect_crate(path).transpose())
.transpose()
}
}

impl CodeUnitDetector for CargoCrateDetector {
fn name(&self) -> &str {
"rust:crate"
}

fn detect(&self, path: &Path) -> anyhow::Result<Option<Rc<dyn CodeUnit>>> {
self.detect_crate(path)
.map(|opt| opt.map(|pkg| pkg as Rc<dyn CodeUnit>))
Expand Down
22 changes: 21 additions & 1 deletion crates/ring-rust/src/crates/source_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,49 @@ use ring_core::{CodeLanguage, CodeUnit};
use ring_tag::{Tag, Tagged};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use crate::CargoCrate;

/// Represents a rust source file
#[derive(Clone, Debug)]
pub struct SourceFile {
path: PathBuf,
cargo_crate: Option<Rc<CargoCrate>>,
}

impl SourceFile {
pub fn new(path: PathBuf) -> SourceFile {
SourceFile { path }
SourceFile {
path,
cargo_crate: None,
}
}

/// Returns path to this script
pub fn path(&self) -> &Path {
&self.path
}

pub fn cargo_crate(&self) -> Option<&Rc<CargoCrate>> {
self.cargo_crate.as_ref()
}

pub fn cargo_crate_mut(&mut self) -> &mut Option<Rc<CargoCrate>> {
&mut self.cargo_crate
}
}

impl CodeUnit for SourceFile {
fn language(&self) -> CodeLanguage {
rust_language()
}

fn parent(&self) -> Option<Rc<dyn CodeUnit>> {
self.cargo_crate
.clone()
.map(|c| c as Rc<dyn CodeUnit>)
}

fn path(&self) -> &Path {
&self.path
}
Expand Down
34 changes: 24 additions & 10 deletions crates/ring-rust/src/crates/source_file_detector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::SourceFile;
use crate::{CargoCrateDetector, SourceFile};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::OsStr;
Expand All @@ -8,14 +8,18 @@ use tracing::{info, instrument, trace};
use ring_core::{CodeUnit, CodeUnitDetector};

/// Detector for script files
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug)]
pub struct SourceFileDetector {
crate_detector: Rc<CargoCrateDetector>,
sources: RefCell<HashMap<PathBuf, Option<Rc<SourceFile>>>>,
}

impl SourceFileDetector {
pub fn new() -> SourceFileDetector {
Default::default()
pub fn new(crate_detector: Rc<CargoCrateDetector>) -> SourceFileDetector {
SourceFileDetector {
crate_detector,
sources: RefCell::new(HashMap::new()),
}
}

pub fn _is_source_file(&self, path: &Path) -> bool {
Expand All @@ -24,29 +28,39 @@ impl SourceFileDetector {
}

#[instrument(name = "rust.detect_source", skip(self, path))]
pub fn detect_source(&self, path: &Path) -> Option<Rc<SourceFile>> {
pub fn detect_source(&self, path: &Path) -> anyhow::Result<Option<Rc<SourceFile>>> {
if let Some(source_file) = self.sources.borrow().get(path) {
return source_file.clone();
return Ok(source_file.clone());
}

let path = path.to_path_buf();

let result = if self._is_source_file(&path) {
let mut source = SourceFile::new(path.clone());

*source.cargo_crate_mut() = path.parent()
.and_then(|parent| self.crate_detector.search_crate(parent).transpose())
.transpose()?;

info!("recognized {} as a rust source file", path.display());
Some(Rc::new(SourceFile::new(path.clone())))
Some(Rc::new(source))
} else {
None
};

self.sources.borrow_mut().insert(path, None);

result
Ok(result)
}
}

impl CodeUnitDetector for SourceFileDetector {
fn name(&self) -> &str {
"rust:source-file"
}

fn detect(&self, path: &Path) -> anyhow::Result<Option<Rc<dyn CodeUnit>>> {
Ok(self.detect_source(path)
.map(|source| source as Rc<dyn CodeUnit>))
self.detect_source(path)
.map(|opt| opt.map(|src| src as Rc<dyn CodeUnit>))
}
}
10 changes: 6 additions & 4 deletions crates/ring-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ use std::rc::Rc;
pub struct RustModule {
pub cargo_crate_detector: Rc<CargoCrateDetector>,
pub source_file_detector: Rc<SourceFileDetector>,
pub rust_unit_detector: Rc<CombinedCodeUnitDetector<2>>,
pub rust_unit_detector: Rc<CombinedCodeUnitDetector>,

pub cargo_process_detector: Rc<CargoProcessDetector>,
}

impl RustModule {
pub fn new() -> Self {
let cargo_crate_detector = Rc::new(CargoCrateDetector::new());
let source_file_detector = Rc::new(SourceFileDetector::new());
let source_file_detector = Rc::new(SourceFileDetector::new(
cargo_crate_detector.clone(),
));

let cargo_process_detector = Rc::new(CargoProcessDetector::new(
cargo_crate_detector.clone(),
Expand All @@ -27,10 +29,10 @@ impl RustModule {
RustModule {
cargo_crate_detector: cargo_crate_detector.clone(),
source_file_detector: source_file_detector.clone(),
rust_unit_detector: Rc::new([
rust_unit_detector: Rc::new(CombinedCodeUnitDetector::new("rust".to_string(), &[
cargo_crate_detector,
source_file_detector,
]),
])),

cargo_process_detector,
}
Expand Down
22 changes: 18 additions & 4 deletions crates/ring-tag/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,30 @@ impl Tag {

/// Displays colored tag using crossterm, according to supported colors of stdout
#[cfg(feature = "crossterm")]
pub fn stylize(&self) -> crossterm::style::StyledContent<String> {
pub fn stylize(&self) -> crossterm::style::StyledContent<&String> {
self.stylize_for(supports_color::Stream::Stdout)
}

/// Displays colored tag using crossterm, according to supported colors of given stream
#[cfg(feature = "crossterm")]
pub fn stylize_for(&self, stream: supports_color::Stream) -> crossterm::style::StyledContent<String> {
pub fn stylize_for(&self, stream: supports_color::Stream) -> crossterm::style::StyledContent<&String> {
self.color
.map(|color| color.stylize_for(self.label.clone(), stream))
.unwrap_or_else(|| crossterm::style::Stylize::stylize(self.label.clone()))
.map(|color| color.style_for(stream)).unwrap_or_default()
.apply(&self.label)
}

/// Displays colored tag using crossterm, according to supported colors of stdout
#[cfg(feature = "crossterm")]
pub fn stylize_full(&self) -> crossterm::style::StyledContent<&Tag> {
self.stylize_full_for(supports_color::Stream::Stdout)
}

/// Displays colored tag using crossterm, according to supported colors of given stream
#[cfg(feature = "crossterm")]
pub fn stylize_full_for(&self, stream: supports_color::Stream) -> crossterm::style::StyledContent<&Tag> {
self.color
.map(|color| color.style_for(stream)).unwrap_or_default()
.apply(self)
}

pub fn label(&self) -> &str {
Expand Down
Loading

0 comments on commit 3bc4acb

Please sign in to comment.